| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- // Licensed to Cloudera, Inc. under one
- // or more contributor license agreements. See the NOTICE file
- // distributed with this work for additional information
- // regarding copyright ownership. Cloudera, Inc. licenses this file
- // to you under the Apache License, Version 2.0 (the
- // "License"); you may not use this file except in compliance
- // with the License. You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /* eslint-disable no-restricted-syntax */
- const extractorUtils = require('./extractorUtils');
- const EXTERNAL_DOC_URL_PREFIX = 'https://www.cloudera.com/documentation/enterprise/latest/';
- const LOG_NAME = 'topicLinker.js';
- /**
- * Creates an external link
- *
- * @param node
- * @param {string} href
- * @param {string} [cssClassPrefix]
- */
- const replaceWithExternalLink = (node, href, cssClassPrefix) => {
- node.name('a');
- extractorUtils.removeAllAttributes(node);
- node.attr({ class: cssClassPrefix + 'doc-external-link', href: href, target: '_blank' });
- };
- /**
- * Creates an internal link if the the ref is part of the topic tree, otherwise it will create an external link.
- *
- * @param node
- * @param {string} ref
- * @param {string} anchorId
- * @param {DitamapParseResult[]} parseResults
- * @param {string} cssClassPrefix
- */
- const replaceWithInternalLink = (node, ref, anchorId, parseResults, cssClassPrefix) => {
- const fullRef = ref + (anchorId ? '#' + anchorId : '');
- const fragmentSearchResult = extractorUtils.findFragment(parseResults, ref, anchorId);
- if (fragmentSearchResult.fragment && fragmentSearchResult.partOfTree) {
- // Here the topic is parsed and it's part of the main topic tree
- node.name('a');
- extractorUtils.removeAllAttributes(node);
- node.attr({
- class: cssClassPrefix + 'doc-internal-link',
- href: 'javascript:void(0);',
- 'data-doc-ref': ref
- });
- if (anchorId) {
- node.attr('data-doc-anchor-id', anchorId);
- }
- } else {
- // Here the topic is unknown or not part of the main topic tree so we make an external link instead
- const href = EXTERNAL_DOC_URL_PREFIX + fullRef.replace('.xml', '.html');
- replaceWithExternalLink(node, href, cssClassPrefix);
- }
- if (!node.text().trim()) {
- if (fragmentSearchResult.fragment) {
- fragmentSearchResult.fragment.title.childNodes().forEach(titleChild => {
- node.addChild(titleChild.clone());
- });
- } else {
- console.log('%s: Could not find suitable text for: %s#%s', LOG_NAME, ref, anchorId);
- }
- }
- };
- /**
- * Replaces keyword nodes with they're specified value.
- *
- * @param node
- * @param {DitamapParseResult[]} parseResults
- * @param {String} cssClassPrefix
- */
- const handleKeywordNode = (node, parseResults, cssClassPrefix) => {
- if (!extractorUtils.hasAttributes(node, 'keyref')) {
- throw new Error('Found keyword without keyref attribute.');
- return;
- }
- const keyRef = node.attr('keyref').value();
- node.attr('keyref').remove();
- let keyDef = undefined;
- parseResults.some(parseResult => {
- if (parseResult.keyDefs[keyRef]) {
- keyDef = parseResult.keyDefs[keyRef];
- return true;
- }
- });
- if (!keyDef) {
- // Here there's no corresponding key definition parsed
- if (node.text().trim()) {
- node.replace(node.text());
- } else if (keyRef === 'distro') {
- // The 'distro' keyword is likely in the top level of the docs, and not part of any sub-maps so default to 'CDH'.
- node.replace('CDH');
- } else {
- // Haven't seen this case yet
- throw new Error('Unknown keyref found: ' + keyRef);
- }
- return;
- }
- if (keyDef.text && !keyDef.href) {
- // A simple reference to a text
- node.replace(keyDef.text);
- return;
- }
- if (keyDef.href) {
- if (keyDef.external) {
- replaceWithExternalLink(node, keyDef.href, cssClassPrefix);
- if (!node.text().trim()) {
- node.text(keyDef.text || keyDef.href);
- }
- } else {
- if (keyDef.href.indexOf('scalability_file_handle_cache') !== -1) {
- }
- replaceWithInternalLink(
- node,
- keyDef.href.replace(/#.*$/, ''),
- keyRef,
- parseResults,
- cssClassPrefix
- );
- }
- return;
- }
- throw new Error('Failed handling keyword node.');
- };
- /**
- * Replaces xref nodes with links
- *
- * @param node
- * @param {DitamapParseResult[]} parseResults
- * @param {string} cssClassPrefix
- */
- const handkeXrefNode = (node, parseResults, cssClassPrefix) => {
- if (extractorUtils.hasAttributes(node, 'href')) {
- const href = node.attr('href').value();
- if (node.attr('scope') && node.attr('scope').value() === 'external') {
- replaceWithExternalLink(node, href, cssClassPrefix);
- if (!node.text()) {
- node.text(href);
- }
- return;
- }
- const ref = ~href.indexOf('#') ? href.replace(/#.*$/, '') : href;
- const anchorId = ~href.indexOf('#') && href.replace(/^.*#/, '');
- replaceWithInternalLink(node, ref, anchorId, parseResults, cssClassPrefix);
- }
- if (extractorUtils.hasAttributes(node, 'keyref')) {
- handleKeywordNode(node, parseResults, cssClassPrefix);
- }
- };
- /**
- * Generates a table of contents if the topic has children
- *
- * @param {Topic} topic
- * @param node
- */
- const handleTocNode = (topic, node) => {
- if (topic.children.length) {
- node.name('div');
- const header = node.node('div');
- header.text('Continue reading:');
- const ul = node.node('ul');
- topic.children.forEach(childTopic => {
- const li = ul.node('li');
- const xrefNode = li.node('xref');
- xrefNode.attr('href', childTopic.ref);
- });
- } else {
- node.remove();
- }
- };
- /**
- * Recursively identifies and replaces certain elements with the previous parse results.
- *
- * @param node
- * @param {DitamapParseResult[]} parseResults
- * @param {string} cssClassPrefix
- * @param {Object} foundCssClasses - Map keeping track of all the added css classes
- * @return {Object} - The map of added css classes
- */
- const linkNodesInDomXml = (node, parseResults, cssClassPrefix, foundCssClasses) => {
- switch (node.name()) {
- case 'keyword':
- handleKeywordNode(node, parseResults, cssClassPrefix);
- break;
- case 'xref':
- handkeXrefNode(node, parseResults, cssClassPrefix);
- break;
- case 'image':
- throw new Error('Unsupported image element found');
- case 'imagemap':
- throw new Error('Unsupported imagemap element found');
- default:
- break;
- }
- if (extractorUtils.hasAttributes(node, 'class')) {
- foundCssClasses[node.attr('class').value()] = true;
- }
- node
- .childNodes()
- .forEach(childNode =>
- linkNodesInDomXml(childNode, parseResults, cssClassPrefix, foundCssClasses)
- );
- return foundCssClasses;
- };
- /**
- * Adds conrefs and table of contents before the linking of the topics.
- *
- * @param {Topic} topic
- * @param node
- * @param {DitamapParseResult[]} parseResults
- */
- const insertConrefsAndToc = (topic, node, parseResults) => {
- if (node.name() === 'toc') {
- handleTocNode(topic, node);
- }
- if (extractorUtils.hasAttributes(node, 'conref')) {
- const conref = node.attr('conref').value();
- const splitRef = conref.split('#');
- const fragmentSearchResult = extractorUtils.findFragment(
- parseResults,
- splitRef[0],
- splitRef[1]
- );
- if (!fragmentSearchResult.fragment) {
- console.log(node.toString());
- console.log('%s: Could not find fragment for conref: %s', LOG_NAME, conref);
- } else {
- node = node.replace(fragmentSearchResult.fragment.domElement.clone());
- if (extractorUtils.hasAttributes(node, 'id')) {
- node.attr('id').remove();
- }
- }
- }
- node.childNodes().forEach(childNode => insertConrefsAndToc(topic, childNode, parseResults));
- };
- /**
- * Links all the nodes in a topic
- *
- * @param {Topic} topic
- * @param {DitamapParseResult[]} parseResults
- * @param {String} cssClassPrefix
- * @param {Object} foundCssClasses
- */
- const linkNodesInTopic = (topic, parseResults, cssClassPrefix, foundCssClasses) => {
- // First insert all the conrefs and topics
- insertConrefsAndToc(topic, topic.domXml, parseResults);
- // Then deal with xrefs, keywords etc.
- linkNodesInDomXml(topic.domXml, parseResults, cssClassPrefix, foundCssClasses);
- topic.children.forEach(childTopic =>
- linkNodesInTopic(childTopic, parseResults, cssClassPrefix, foundCssClasses)
- );
- };
- /**
- * This links all the nodes after parsing, it replaces keywords, adds links for xrefs, builds table of contents etc.
- *
- * @param parseResults
- * @param cssClassPrefix
- */
- const linkTopics = (parseResults, cssClassPrefix) => {
- const foundCssClasses = {};
- parseResults.forEach(parseResult =>
- parseResult.topics.forEach(topic => {
- linkNodesInTopic(topic, parseResults, cssClassPrefix, foundCssClasses);
- })
- );
- console.log('%s: Found CSS classes: %s', LOG_NAME, Object.keys(foundCssClasses).join(','));
- };
- module.exports = {
- linkTopics: linkTopics
- };
- /* eslint-enable no-restricted-syntax */
|