ditamapParser.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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. /* eslint-disable no-restricted-syntax */
  17. const libxml = require('libxmljs');
  18. const Topic = require('./Topic');
  19. const extractorUtils = require('./extractorUtils');
  20. const LOG_NAME = 'ditamapParser.js: ';
  21. /**
  22. * @typedef {Object} KeyDef
  23. * @property {string} [text]
  24. * @property {string} [href]
  25. * @property {boolean} [external]
  26. */
  27. /**
  28. * @typedef {Object} DitamapParseResult
  29. * @property {Topic[]} topics - The topic tree
  30. * @property {Object} topicIndex - Key value pairs of all topics ('ref': Topic)
  31. * @property {Object.<string, KeyDef>} keyDefs - Key value pairs of key definitions, i.e. { 'impala23': { text: 'Impala 2.3' } }
  32. */
  33. /**
  34. * Extracts topics from a given ditamap file
  35. *
  36. * @param {string} ditamapFile
  37. * @param {string} docRootPath - The root path of the documents
  38. *
  39. * @return {Promise<DitamapParseResult>} - A promise of the Topic tree and index
  40. */
  41. const parseDitamap = (ditamapFile, docRootPath) =>
  42. new Promise((resolve, reject) => {
  43. const parseResult = {
  44. topics: [],
  45. topicIndex: {},
  46. keyDefs: {}
  47. };
  48. extractFromDitamapFile(ditamapFile, docRootPath, parseResult)
  49. .then(() => {
  50. resolve(parseResult);
  51. })
  52. .catch(reject);
  53. });
  54. const extractFromDitamapFile = (ditamapFile, docRootPath, parseResult) =>
  55. new Promise((resolve, reject) => {
  56. extractorUtils
  57. .readFile(docRootPath + ditamapFile)
  58. .then(contents => {
  59. const mapNode = libxml.parseXmlString(contents).get('//map');
  60. extractFromMapNode(mapNode, ditamapFile, docRootPath, parseResult)
  61. .then(resolve)
  62. .catch(reject);
  63. })
  64. .catch(reject);
  65. });
  66. const extractFromMapNode = (mapNode, ditamapFile, docRootPath, parseResult) => {
  67. const promises = [];
  68. const handleMapNodeChildren = (childNodes, currentTopic) => {
  69. childNodes.forEach(node => {
  70. switch (node.name()) {
  71. case 'topicref': {
  72. if (extractorUtils.hasAttributes(node, 'href')) {
  73. if (~node.attr('href').value().indexOf('.ditamap')) {
  74. promises.push(
  75. extractFromDitamapFile(node.attr('href').value(), docRootPath, parseResult)
  76. );
  77. break;
  78. }
  79. const topic = new Topic(docRootPath, node.attr('href').value());
  80. if (currentTopic) {
  81. currentTopic.children.push(topic);
  82. } else {
  83. parseResult.topics.push(topic);
  84. }
  85. parseResult.topicIndex[node.attr('href').value().replace(/#.*$/, '')] = topic;
  86. handleMapNodeChildren(node.childNodes(), topic);
  87. } else {
  88. console.log(
  89. '%s: Couldn\'t handle "topicref" node: %s in file %s%s',
  90. LOG_NAME,
  91. node.toString(),
  92. docRootPath,
  93. ditamapFile
  94. );
  95. }
  96. break;
  97. }
  98. case 'mapref': {
  99. if (extractorUtils.hasAttributes(node, 'href')) {
  100. promises.push(
  101. extractFromDitamapFile(node.attr('href').value(), docRootPath, parseResult)
  102. );
  103. } else {
  104. console.log(
  105. '%s: Couldn\'t handle "mapref" node: \n%s in file %s%s',
  106. LOG_NAME,
  107. node.toString(),
  108. docRootPath,
  109. ditamapFile
  110. );
  111. }
  112. break;
  113. }
  114. case 'keydef':
  115. if (extractorUtils.hasAttributes(node, 'keys')) {
  116. const valNode = node.get('topicmeta/keywords/keyword');
  117. if (valNode) {
  118. parseResult.keyDefs[node.attr('keys').value()] = { text: valNode.text() };
  119. } else if (node.attr('href')) {
  120. if (!node.attr('href').value() && node.text().trim()) {
  121. parseResult.keyDefs[node.attr('keys').value()] = { text: node.text() };
  122. } else {
  123. parseResult.keyDefs[node.attr('keys').value()] = {
  124. href: node.attr('href').value(),
  125. external: node.attr('scope') && node.attr('scope').value() === 'external'
  126. };
  127. }
  128. }
  129. }
  130. case 'comment':
  131. case 'text':
  132. case 'title':
  133. case 'topichead':
  134. case 'topicmeta':
  135. break;
  136. default:
  137. console.log(
  138. "%s: Couldn't handle map node: \n%s in file %s%s",
  139. LOG_NAME,
  140. node.toString(),
  141. docRootPath,
  142. ditamapFile
  143. );
  144. }
  145. });
  146. };
  147. handleMapNodeChildren(mapNode.childNodes());
  148. return Promise.all(promises);
  149. };
  150. module.exports = {
  151. parseDitamap: parseDitamap
  152. };
  153. /* eslint-enable no-restricted-syntax */