ditamapParser.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. const libxml = require('libxmljs');
  17. const Topic = require('./Topic');
  18. const extractorUtils = require('./extractorUtils');
  19. const LOG_NAME = 'ditamapParser.js: ';
  20. /**
  21. * @typedef {Object} KeyDef
  22. * @property {string} [text]
  23. * @property {string} [href]
  24. * @property {boolean} [external]
  25. */
  26. /**
  27. * @typedef {Object} DitamapParseResult
  28. * @property {Topic[]} topics - The topic tree
  29. * @property {Object} topicIndex - Key value pairs of all topics ('ref': Topic)
  30. * @property {Object.<string, KeyDef>} keyDefs - Key value pairs of key definitions, i.e. { 'impala23': { text: 'Impala 2.3' } }
  31. */
  32. /**
  33. * Extracts topics from a given ditamap file
  34. *
  35. * @param {string} ditamapFile
  36. * @param {string} docRootPath - The root path of the documents
  37. *
  38. * @return {Promise<DitamapParseResult>} - A promise of the Topic tree and index
  39. */
  40. const parseDitamap = (ditamapFile, docRootPath) => new Promise((resolve, reject) => {
  41. let parseResult = {
  42. topics: [],
  43. topicIndex: {},
  44. keyDefs: {}
  45. };
  46. extractFromDitamapFile(ditamapFile, docRootPath, parseResult).then(() => {
  47. resolve(parseResult);
  48. }).catch(reject);
  49. });
  50. const extractFromDitamapFile = (ditamapFile, docRootPath, parseResult) => new Promise((resolve, reject) => {
  51. extractorUtils.readFile(docRootPath + ditamapFile).then(contents => {
  52. let mapNode = libxml.parseXmlString(contents).get('//map');
  53. extractFromMapNode(mapNode, ditamapFile, docRootPath, parseResult).then(resolve).catch(reject);
  54. }).catch(reject);
  55. });
  56. const extractFromMapNode = (mapNode, ditamapFile, docRootPath, parseResult) => {
  57. let promises = [];
  58. let handleMapNodeChildren = (childNodes, currentTopic) => {
  59. childNodes.forEach(node => {
  60. switch (node.name()) {
  61. case 'topicref': {
  62. if (extractorUtils.hasAttributes(node, 'href')) {
  63. if (~node.attr('href').value().indexOf('.ditamap')) {
  64. promises.push(extractFromDitamapFile(node.attr('href').value(), docRootPath, parseResult));
  65. break;
  66. }
  67. let topic = new Topic(docRootPath, node.attr('href').value());
  68. if (currentTopic) {
  69. currentTopic.children.push(topic);
  70. } else {
  71. parseResult.topics.push(topic);
  72. }
  73. parseResult.topicIndex[node.attr('href').value().replace(/#.*$/, '')] = topic;
  74. handleMapNodeChildren(node.childNodes(), topic);
  75. } else {
  76. console.log('%s: Couldn\'t handle "topicref" node: %s in file %s%s', LOG_NAME, node.toString(), docRootPath, ditamapFile);
  77. }
  78. break;
  79. }
  80. case 'mapref': {
  81. if (extractorUtils.hasAttributes(node, 'href')) {
  82. promises.push(extractFromDitamapFile(node.attr('href').value(), docRootPath, parseResult));
  83. } else {
  84. console.log('%s: Couldn\'t handle "mapref" node: \n%s in file %s%s', LOG_NAME, node.toString(), docRootPath, ditamapFile);
  85. }
  86. break;
  87. }
  88. case 'keydef':
  89. if (extractorUtils.hasAttributes(node, 'keys')) {
  90. let valNode = node.get('topicmeta/keywords/keyword');
  91. if (valNode) {
  92. parseResult.keyDefs[node.attr('keys').value()] = { text: valNode.text() };
  93. } else if (node.attr('href')) {
  94. if (!node.attr('href').value() && node.text().trim()) {
  95. parseResult.keyDefs[node.attr('keys').value()] = { text: node.text() };
  96. } else {
  97. parseResult.keyDefs[node.attr('keys').value()] = {
  98. href: node.attr('href').value(),
  99. external: node.attr('scope') && node.attr('scope').value() === 'external'
  100. }
  101. }
  102. }
  103. }
  104. case 'comment':
  105. case 'text':
  106. case 'title':
  107. case 'topichead':
  108. case 'topicmeta':
  109. break;
  110. default:
  111. console.log('%s: Couldn\'t handle map node: \n%s in file %s%s', LOG_NAME, node.toString(), docRootPath, ditamapFile);
  112. }
  113. })
  114. };
  115. handleMapNodeChildren(mapNode.childNodes());
  116. return Promise.all(promises);
  117. };
  118. module.exports = {
  119. parseDitamap: parseDitamap
  120. };