ditamapParser.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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) =>
  41. new Promise((resolve, reject) => {
  42. const parseResult = {
  43. topics: [],
  44. topicIndex: {},
  45. keyDefs: {}
  46. };
  47. extractFromDitamapFile(ditamapFile, docRootPath, parseResult)
  48. .then(() => {
  49. resolve(parseResult);
  50. })
  51. .catch(reject);
  52. });
  53. const extractFromDitamapFile = (ditamapFile, docRootPath, parseResult) =>
  54. new Promise((resolve, reject) => {
  55. extractorUtils
  56. .readFile(docRootPath + ditamapFile)
  57. .then(contents => {
  58. const mapNode = libxml.parseXmlString(contents).get('//map');
  59. extractFromMapNode(mapNode, ditamapFile, docRootPath, parseResult)
  60. .then(resolve)
  61. .catch(reject);
  62. })
  63. .catch(reject);
  64. });
  65. const extractFromMapNode = (mapNode, ditamapFile, docRootPath, parseResult) => {
  66. const promises = [];
  67. const handleMapNodeChildren = (childNodes, currentTopic) => {
  68. childNodes.forEach(node => {
  69. switch (node.name()) {
  70. case 'topicref': {
  71. if (extractorUtils.hasAttributes(node, 'href')) {
  72. if (
  73. ~node
  74. .attr('href')
  75. .value()
  76. .indexOf('.ditamap')
  77. ) {
  78. promises.push(
  79. extractFromDitamapFile(node.attr('href').value(), docRootPath, parseResult)
  80. );
  81. break;
  82. }
  83. const topic = new Topic(docRootPath, node.attr('href').value());
  84. if (currentTopic) {
  85. currentTopic.children.push(topic);
  86. } else {
  87. parseResult.topics.push(topic);
  88. }
  89. parseResult.topicIndex[
  90. node
  91. .attr('href')
  92. .value()
  93. .replace(/#.*$/, '')
  94. ] = topic;
  95. handleMapNodeChildren(node.childNodes(), topic);
  96. } else {
  97. console.log(
  98. '%s: Couldn\'t handle "topicref" node: %s in file %s%s',
  99. LOG_NAME,
  100. node.toString(),
  101. docRootPath,
  102. ditamapFile
  103. );
  104. }
  105. break;
  106. }
  107. case 'mapref': {
  108. if (extractorUtils.hasAttributes(node, 'href')) {
  109. promises.push(
  110. extractFromDitamapFile(node.attr('href').value(), docRootPath, parseResult)
  111. );
  112. } else {
  113. console.log(
  114. '%s: Couldn\'t handle "mapref" node: \n%s in file %s%s',
  115. LOG_NAME,
  116. node.toString(),
  117. docRootPath,
  118. ditamapFile
  119. );
  120. }
  121. break;
  122. }
  123. case 'keydef':
  124. if (extractorUtils.hasAttributes(node, 'keys')) {
  125. const valNode = node.get('topicmeta/keywords/keyword');
  126. if (valNode) {
  127. parseResult.keyDefs[node.attr('keys').value()] = { text: valNode.text() };
  128. } else if (node.attr('href')) {
  129. if (!node.attr('href').value() && node.text().trim()) {
  130. parseResult.keyDefs[node.attr('keys').value()] = { text: node.text() };
  131. } else {
  132. parseResult.keyDefs[node.attr('keys').value()] = {
  133. href: node.attr('href').value(),
  134. external: node.attr('scope') && node.attr('scope').value() === 'external'
  135. };
  136. }
  137. }
  138. }
  139. case 'comment':
  140. case 'text':
  141. case 'title':
  142. case 'topichead':
  143. case 'topicmeta':
  144. break;
  145. default:
  146. console.log(
  147. "%s: Couldn't handle map node: \n%s in file %s%s",
  148. LOG_NAME,
  149. node.toString(),
  150. docRootPath,
  151. ditamapFile
  152. );
  153. }
  154. });
  155. };
  156. handleMapNodeChildren(mapNode.childNodes());
  157. return Promise.all(promises);
  158. };
  159. module.exports = {
  160. parseDitamap: parseDitamap
  161. };