ditamapParser.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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 (
  74. ~node
  75. .attr('href')
  76. .value()
  77. .indexOf('.ditamap')
  78. ) {
  79. promises.push(
  80. extractFromDitamapFile(node.attr('href').value(), docRootPath, parseResult)
  81. );
  82. break;
  83. }
  84. const topic = new Topic(docRootPath, node.attr('href').value());
  85. if (currentTopic) {
  86. currentTopic.children.push(topic);
  87. } else {
  88. parseResult.topics.push(topic);
  89. }
  90. parseResult.topicIndex[
  91. node
  92. .attr('href')
  93. .value()
  94. .replace(/#.*$/, '')
  95. ] = topic;
  96. handleMapNodeChildren(node.childNodes(), topic);
  97. } else {
  98. console.log(
  99. '%s: Couldn\'t handle "topicref" node: %s in file %s%s',
  100. LOG_NAME,
  101. node.toString(),
  102. docRootPath,
  103. ditamapFile
  104. );
  105. }
  106. break;
  107. }
  108. case 'mapref': {
  109. if (extractorUtils.hasAttributes(node, 'href')) {
  110. promises.push(
  111. extractFromDitamapFile(node.attr('href').value(), docRootPath, parseResult)
  112. );
  113. } else {
  114. console.log(
  115. '%s: Couldn\'t handle "mapref" node: \n%s in file %s%s',
  116. LOG_NAME,
  117. node.toString(),
  118. docRootPath,
  119. ditamapFile
  120. );
  121. }
  122. break;
  123. }
  124. case 'keydef':
  125. if (extractorUtils.hasAttributes(node, 'keys')) {
  126. const valNode = node.get('topicmeta/keywords/keyword');
  127. if (valNode) {
  128. parseResult.keyDefs[node.attr('keys').value()] = { text: valNode.text() };
  129. } else if (node.attr('href')) {
  130. if (!node.attr('href').value() && node.text().trim()) {
  131. parseResult.keyDefs[node.attr('keys').value()] = { text: node.text() };
  132. } else {
  133. parseResult.keyDefs[node.attr('keys').value()] = {
  134. href: node.attr('href').value(),
  135. external: node.attr('scope') && node.attr('scope').value() === 'external'
  136. };
  137. }
  138. }
  139. }
  140. case 'comment':
  141. case 'text':
  142. case 'title':
  143. case 'topichead':
  144. case 'topicmeta':
  145. break;
  146. default:
  147. console.log(
  148. "%s: Couldn't handle map node: \n%s in file %s%s",
  149. LOG_NAME,
  150. node.toString(),
  151. docRootPath,
  152. ditamapFile
  153. );
  154. }
  155. });
  156. };
  157. handleMapNodeChildren(mapNode.childNodes());
  158. return Promise.all(promises);
  159. };
  160. module.exports = {
  161. parseDitamap: parseDitamap
  162. };
  163. /* eslint-enable no-restricted-syntax */