@hyperlex/mammoth 1.4.9-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.eslintrc.json +77 -0
  2. package/.github/ISSUE_TEMPLATE.md +12 -0
  3. package/.idea/mammoth.js.iml +12 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/.travis.yml +10 -0
  7. package/LICENSE +22 -0
  8. package/NEWS +373 -0
  9. package/README.md +883 -0
  10. package/bin/mammoth +38 -0
  11. package/browser/docx/files.js +14 -0
  12. package/browser/unzip.js +12 -0
  13. package/lib/document-to-html.js +453 -0
  14. package/lib/documents.js +238 -0
  15. package/lib/docx/body-reader.js +636 -0
  16. package/lib/docx/comments-reader.js +31 -0
  17. package/lib/docx/content-types-reader.js +58 -0
  18. package/lib/docx/document-xml-reader.js +26 -0
  19. package/lib/docx/docx-reader.js +222 -0
  20. package/lib/docx/files.js +67 -0
  21. package/lib/docx/notes-reader.js +28 -0
  22. package/lib/docx/numbering-xml.js +69 -0
  23. package/lib/docx/office-xml-reader.js +58 -0
  24. package/lib/docx/relationships-reader.js +43 -0
  25. package/lib/docx/style-map.js +75 -0
  26. package/lib/docx/styles-reader.js +70 -0
  27. package/lib/docx/uris.js +21 -0
  28. package/lib/html/ast.js +50 -0
  29. package/lib/html/index.js +41 -0
  30. package/lib/html/simplify.js +88 -0
  31. package/lib/images.js +29 -0
  32. package/lib/index.js +115 -0
  33. package/lib/main.js +63 -0
  34. package/lib/options-reader.js +98 -0
  35. package/lib/promises.js +42 -0
  36. package/lib/results.js +72 -0
  37. package/lib/style-reader.js +321 -0
  38. package/lib/styles/document-matchers.js +74 -0
  39. package/lib/styles/html-paths.js +81 -0
  40. package/lib/styles/parser/tokeniser.js +30 -0
  41. package/lib/transforms.js +61 -0
  42. package/lib/underline.js +11 -0
  43. package/lib/unzip.js +22 -0
  44. package/lib/writers/html-writer.js +160 -0
  45. package/lib/writers/index.js +14 -0
  46. package/lib/writers/markdown-writer.js +163 -0
  47. package/lib/xml/index.js +7 -0
  48. package/lib/xml/nodes.js +69 -0
  49. package/lib/xml/reader.js +83 -0
  50. package/lib/xml/writer.js +61 -0
  51. package/lib/zipfile.js +77 -0
  52. package/mammoth.browser.js +32950 -0
  53. package/mammoth.browser.min.js +18 -0
  54. package/package.json +65 -0
  55. package/test/.eslintrc.json +7 -0
  56. package/test/document-to-html.tests.js +834 -0
  57. package/test/docx/body-reader.tests.js +1342 -0
  58. package/test/docx/comments-reader.tests.js +52 -0
  59. package/test/docx/content-types-reader.tests.js +45 -0
  60. package/test/docx/document-matchers.js +37 -0
  61. package/test/docx/docx-reader.tests.js +179 -0
  62. package/test/docx/files.tests.js +94 -0
  63. package/test/docx/notes-reader.tests.js +35 -0
  64. package/test/docx/numbering-xml.tests.js +65 -0
  65. package/test/docx/office-xml-reader.tests.js +24 -0
  66. package/test/docx/relationships-reader.tests.js +65 -0
  67. package/test/docx/style-map.tests.js +112 -0
  68. package/test/docx/styles-reader.tests.js +133 -0
  69. package/test/docx/uris.tests.js +22 -0
  70. package/test/html/simplify.tests.js +134 -0
  71. package/test/html/write.tests.js +42 -0
  72. package/test/images.tests.js +34 -0
  73. package/test/main.tests.js +89 -0
  74. package/test/mammoth.tests.js +429 -0
  75. package/test/mocha.opts +1 -0
  76. package/test/options-reader.tests.js +63 -0
  77. package/test/results.tests.js +15 -0
  78. package/test/style-reader.tests.js +256 -0
  79. package/test/styles/document-matchers.tests.js +71 -0
  80. package/test/styles/html-paths.tests.js +20 -0
  81. package/test/styles/parser/tokeniser.tests.js +104 -0
  82. package/test/test-data/comments.docx +0 -0
  83. package/test/test-data/embedded-style-map.docx +0 -0
  84. package/test/test-data/empty.docx +0 -0
  85. package/test/test-data/empty.zip +0 -0
  86. package/test/test-data/endnotes.docx +0 -0
  87. package/test/test-data/external-picture.docx +0 -0
  88. package/test/test-data/footnote-hyperlink.docx +0 -0
  89. package/test/test-data/footnotes.docx +0 -0
  90. package/test/test-data/hello.zip +0 -0
  91. package/test/test-data/hyperlinks/word/_rels/document.xml.rels +10 -0
  92. package/test/test-data/hyperlinks/word/document.xml +18 -0
  93. package/test/test-data/simple/word/document.xml +18 -0
  94. package/test/test-data/simple-list.docx +0 -0
  95. package/test/test-data/single-paragraph.docx +0 -0
  96. package/test/test-data/strikethrough.docx +0 -0
  97. package/test/test-data/tables.docx +0 -0
  98. package/test/test-data/text-box.docx +0 -0
  99. package/test/test-data/tiny-picture-target-base-relative.docx +0 -0
  100. package/test/test-data/tiny-picture.docx +0 -0
  101. package/test/test-data/tiny-picture.png +0 -0
  102. package/test/test-data/underline.docx +0 -0
  103. package/test/test-data/utf8-bom.docx +0 -0
  104. package/test/test.js +11 -0
  105. package/test/testing.js +55 -0
  106. package/test/transforms.tests.js +125 -0
  107. package/test/unzip.tests.js +38 -0
  108. package/test/writers/html-writer.tests.js +133 -0
  109. package/test/writers/markdown-writer.tests.js +304 -0
  110. package/test/xml/reader.tests.js +85 -0
  111. package/test/xml/writer.tests.js +81 -0
  112. package/test/zipfile.tests.js +59 -0
@@ -0,0 +1,160 @@
1
+ var util = require("util");
2
+ var _ = require("underscore");
3
+
4
+ exports.writer = writer;
5
+
6
+ function writer(options) {
7
+ options = options || {};
8
+ if (options.prettyPrint) {
9
+ return prettyWriter();
10
+ } else {
11
+ return simpleWriter();
12
+ }
13
+ }
14
+
15
+
16
+ var indentedElements = {
17
+ div: true,
18
+ p: true,
19
+ ul: true,
20
+ li: true
21
+ };
22
+
23
+
24
+ function prettyWriter() {
25
+ var indentationLevel = 0;
26
+ var indentation = " ";
27
+ var stack = [];
28
+ var start = true;
29
+ var inText = false;
30
+
31
+ var writer = simpleWriter();
32
+
33
+ function open(tagName, attributes) {
34
+ if (indentedElements[tagName]) {
35
+ indent();
36
+ }
37
+ stack.push(tagName);
38
+ writer.open(tagName, attributes);
39
+ if (indentedElements[tagName]) {
40
+ indentationLevel++;
41
+ }
42
+ start = false;
43
+ }
44
+
45
+ function close(tagName) {
46
+ if (indentedElements[tagName]) {
47
+ indentationLevel--;
48
+ indent();
49
+ }
50
+ stack.pop();
51
+ writer.close(tagName);
52
+ }
53
+
54
+ function text(value) {
55
+ startText();
56
+ var text = isInPre() ? value : value.replace("\n", "\n" + indentation);
57
+ writer.text(text);
58
+ }
59
+
60
+ function selfClosing(tagName, attributes) {
61
+ indent();
62
+ writer.selfClosing(tagName, attributes);
63
+ }
64
+
65
+ function insideIndentedElement() {
66
+ return stack.length === 0 || indentedElements[stack[stack.length - 1]];
67
+ }
68
+
69
+ function startText() {
70
+ if (!inText) {
71
+ indent();
72
+ inText = true;
73
+ }
74
+ }
75
+
76
+ function indent() {
77
+ inText = false;
78
+ if (!start && insideIndentedElement() && !isInPre()) {
79
+ writer._append("\n");
80
+ for (var i = 0; i < indentationLevel; i++) {
81
+ writer._append(indentation);
82
+ }
83
+ }
84
+ }
85
+
86
+ function isInPre() {
87
+ return _.some(stack, function(tagName) {
88
+ return tagName === "pre";
89
+ });
90
+ }
91
+
92
+ return {
93
+ asString: writer.asString,
94
+ open: open,
95
+ close: close,
96
+ text: text,
97
+ selfClosing: selfClosing
98
+ };
99
+ }
100
+
101
+
102
+ function simpleWriter() {
103
+ var fragments = [];
104
+
105
+ function open(tagName, attributes) {
106
+ var attributeString = generateAttributeString(attributes);
107
+ fragments.push(util.format("<%s%s>", tagName, attributeString));
108
+ }
109
+
110
+ function close(tagName) {
111
+ fragments.push(util.format("</%s>", tagName));
112
+ }
113
+
114
+ function selfClosing(tagName, attributes) {
115
+ var attributeString = generateAttributeString(attributes);
116
+ fragments.push(util.format("<%s%s />", tagName, attributeString));
117
+ }
118
+
119
+ function generateAttributeString(attributes) {
120
+ return _.map(attributes, function(value, key) {
121
+ return util.format(' %s="%s"', key, escapeHtmlAttribute(value));
122
+ }).join("");
123
+ }
124
+
125
+ function text(value) {
126
+ fragments.push(escapeHtmlText(value));
127
+ }
128
+
129
+ function append(html) {
130
+ fragments.push(html);
131
+ }
132
+
133
+ function asString() {
134
+ return fragments.join("");
135
+ }
136
+
137
+ return {
138
+ asString: asString,
139
+ open: open,
140
+ close: close,
141
+ text: text,
142
+ selfClosing: selfClosing,
143
+ _append: append
144
+ };
145
+ }
146
+
147
+ function escapeHtmlText(value) {
148
+ return value
149
+ .replace(/&/g, '&amp;')
150
+ .replace(/</g, '&lt;')
151
+ .replace(/>/g, '&gt;');
152
+ }
153
+
154
+ function escapeHtmlAttribute(value) {
155
+ return value
156
+ .replace(/&/g, '&amp;')
157
+ .replace(/"/g, '&quot;')
158
+ .replace(/</g, '&lt;')
159
+ .replace(/>/g, '&gt;');
160
+ }
@@ -0,0 +1,14 @@
1
+ var htmlWriter = require("./html-writer");
2
+ var markdownWriter = require("./markdown-writer");
3
+
4
+ exports.writer = writer;
5
+
6
+
7
+ function writer(options) {
8
+ options = options || {};
9
+ if (options.outputFormat === "markdown") {
10
+ return markdownWriter.writer();
11
+ } else {
12
+ return htmlWriter.writer(options);
13
+ }
14
+ }
@@ -0,0 +1,163 @@
1
+ var _ = require("underscore");
2
+
3
+
4
+ function symmetricMarkdownElement(end) {
5
+ return markdownElement(end, end);
6
+ }
7
+
8
+ function markdownElement(start, end) {
9
+ return function() {
10
+ return {start: start, end: end};
11
+ };
12
+ }
13
+
14
+ function markdownLink(attributes) {
15
+ var href = attributes.href || "";
16
+ if (href) {
17
+ return {
18
+ start: "[",
19
+ end: "](" + href + ")",
20
+ anchorPosition: "before"
21
+ };
22
+ } else {
23
+ return {};
24
+ }
25
+ }
26
+
27
+ function markdownImage(attributes) {
28
+ var src = attributes.src || "";
29
+ var altText = attributes.alt || "";
30
+ if (src || altText) {
31
+ return {start: "![" + altText + "](" + src + ")"};
32
+ } else {
33
+ return {};
34
+ }
35
+ }
36
+
37
+ function markdownList(options) {
38
+ return function(attributes, list) {
39
+ return {
40
+ start: list ? "\n" : "",
41
+ end: list ? "" : "\n",
42
+ list: {
43
+ isOrdered: options.isOrdered,
44
+ indent: list ? list.indent + 1 : 0,
45
+ count: 0
46
+ }
47
+ };
48
+ };
49
+ }
50
+
51
+ function markdownListItem(attributes, list, listItem) {
52
+ list = list || {indent: 0, isOrdered: false, count: 0};
53
+ list.count++;
54
+ listItem.hasClosed = false;
55
+
56
+ var bullet = list.isOrdered ? list.count + "." : "-";
57
+ var start = repeatString("\t", list.indent) + bullet + " ";
58
+
59
+ return {
60
+ start: start,
61
+ end: function() {
62
+ if (!listItem.hasClosed) {
63
+ listItem.hasClosed = true;
64
+ return "\n";
65
+ }
66
+ }
67
+ };
68
+ }
69
+
70
+ var htmlToMarkdown = {
71
+ "p": markdownElement("", "\n\n"),
72
+ "br": markdownElement("", " \n"),
73
+ "ul": markdownList({isOrdered: false}),
74
+ "ol": markdownList({isOrdered: true}),
75
+ "li": markdownListItem,
76
+ "strong": symmetricMarkdownElement("__"),
77
+ "em": symmetricMarkdownElement("*"),
78
+ "a": markdownLink,
79
+ "img": markdownImage
80
+ };
81
+
82
+ (function() {
83
+ for (var i = 1; i <= 6; i++) {
84
+ htmlToMarkdown["h" + i] = markdownElement(repeatString("#", i) + " ", "\n\n");
85
+ }
86
+ })();
87
+
88
+ function repeatString(value, count) {
89
+ return new Array(count + 1).join(value);
90
+ }
91
+
92
+ function markdownWriter() {
93
+ var fragments = [];
94
+ var elementStack = [];
95
+ var list = null;
96
+ var listItem = {};
97
+
98
+ function open(tagName, attributes) {
99
+ attributes = attributes || {};
100
+
101
+ var createElement = htmlToMarkdown[tagName] || function() {
102
+ return {};
103
+ };
104
+ var element = createElement(attributes, list, listItem);
105
+ elementStack.push({end: element.end, list: list});
106
+
107
+ if (element.list) {
108
+ list = element.list;
109
+ }
110
+
111
+ var anchorBeforeStart = element.anchorPosition === "before";
112
+ if (anchorBeforeStart) {
113
+ writeAnchor(attributes);
114
+ }
115
+
116
+ fragments.push(element.start || "");
117
+ if (!anchorBeforeStart) {
118
+ writeAnchor(attributes);
119
+ }
120
+ }
121
+
122
+ function writeAnchor(attributes) {
123
+ if (attributes.id) {
124
+ fragments.push('<a id="' + attributes.id + '"></a>');
125
+ }
126
+ }
127
+
128
+ function close(tagName) {
129
+ var element = elementStack.pop();
130
+ list = element.list;
131
+ var end = _.isFunction(element.end) ? element.end() : element.end;
132
+ fragments.push(end || "");
133
+ }
134
+
135
+ function selfClosing(tagName, attributes) {
136
+ open(tagName, attributes);
137
+ close(tagName);
138
+ }
139
+
140
+ function text(value) {
141
+ fragments.push(escapeMarkdown(value));
142
+ }
143
+
144
+ function asString() {
145
+ return fragments.join("");
146
+ }
147
+
148
+ return {
149
+ asString: asString,
150
+ open: open,
151
+ close: close,
152
+ text: text,
153
+ selfClosing: selfClosing
154
+ };
155
+ }
156
+
157
+ exports.writer = markdownWriter;
158
+
159
+ function escapeMarkdown(value) {
160
+ return value
161
+ .replace(/\\/g, '\\\\')
162
+ .replace(/([\`\*_\{\}\[\]\(\)\#\+\-\.\!])/g, '\\$1');
163
+ }
@@ -0,0 +1,7 @@
1
+ var nodes = require("./nodes");
2
+
3
+ exports.Element = nodes.Element;
4
+ exports.element = nodes.element;
5
+ exports.text = nodes.text;
6
+ exports.readString = require("./reader").readString;
7
+ exports.writeString = require("./writer").writeString;
@@ -0,0 +1,69 @@
1
+ var _ = require("underscore");
2
+
3
+
4
+ exports.Element = Element;
5
+ exports.element = function(name, attributes, children) {
6
+ return new Element(name, attributes, children);
7
+ };
8
+ exports.text = function(value) {
9
+ return {
10
+ type: "text",
11
+ value: value
12
+ };
13
+ };
14
+
15
+
16
+ var emptyElement = {
17
+ first: function() {
18
+ return null;
19
+ },
20
+ firstOrEmpty: function() {
21
+ return emptyElement;
22
+ },
23
+ attributes: {}
24
+ };
25
+
26
+ function Element(name, attributes, children) {
27
+ this.type = "element";
28
+ this.name = name;
29
+ this.attributes = attributes || {};
30
+ this.children = children || [];
31
+ }
32
+
33
+ Element.prototype.first = function(name) {
34
+ return _.find(this.children, function(child) {
35
+ return child.name === name;
36
+ });
37
+ };
38
+
39
+ Element.prototype.firstOrEmpty = function(name) {
40
+ return this.first(name) || emptyElement;
41
+ };
42
+
43
+ Element.prototype.getElementsByTagName = function(name) {
44
+ var elements = _.filter(this.children, function(child) {
45
+ return child.name === name;
46
+ });
47
+ return toElementList(elements);
48
+ };
49
+
50
+ Element.prototype.text = function() {
51
+ if (this.children.length === 0) {
52
+ return "";
53
+ } else if (this.children.length !== 1 || this.children[0].type !== "text") {
54
+ throw new Error("Not implemented");
55
+ }
56
+ return this.children[0].value;
57
+ };
58
+
59
+ var elementListPrototype = {
60
+ getElementsByTagName: function(name) {
61
+ return toElementList(_.flatten(this.map(function(element) {
62
+ return element.getElementsByTagName(name);
63
+ }, true)));
64
+ }
65
+ };
66
+
67
+ function toElementList(array) {
68
+ return _.extend(array, elementListPrototype);
69
+ }
@@ -0,0 +1,83 @@
1
+ var promises = require("../promises");
2
+ var sax = require("sax");
3
+ var _ = require("underscore");
4
+
5
+ var nodes = require("./nodes");
6
+ var Element = nodes.Element;
7
+
8
+ exports.readString = readString;
9
+
10
+ function readString(xmlString, namespaceMap) {
11
+ namespaceMap = namespaceMap || {};
12
+
13
+ var finished = false;
14
+ var parser = sax.parser(true, {xmlns: true, position: false});
15
+
16
+ var rootElement = {children: []};
17
+ var currentElement = rootElement;
18
+ var stack = [];
19
+
20
+ var deferred = promises.defer();
21
+
22
+ parser.onopentag = function(node) {
23
+ var attributes = mapObject(node.attributes, function(attribute) {
24
+ return attribute.value;
25
+ }, mapName);
26
+
27
+ var element = new Element(mapName(node), attributes);
28
+ currentElement.children.push(element);
29
+ stack.push(currentElement);
30
+ currentElement = element;
31
+ };
32
+
33
+ function mapName(node) {
34
+ if (node.uri) {
35
+ var mappedPrefix = namespaceMap[node.uri];
36
+ var prefix;
37
+ if (mappedPrefix) {
38
+ prefix = mappedPrefix + ":";
39
+ } else {
40
+ prefix = "{" + node.uri + "}";
41
+ }
42
+ return prefix + node.local;
43
+ } else {
44
+ return node.local;
45
+ }
46
+ }
47
+
48
+ parser.onclosetag = function(node) {
49
+ currentElement = stack.pop();
50
+ };
51
+
52
+ parser.ontext = function(text) {
53
+ if (currentElement !== rootElement) {
54
+ currentElement.children.push(nodes.text(text));
55
+ }
56
+ };
57
+
58
+ parser.onend = function() {
59
+ if (!finished) {
60
+ finished = true;
61
+ deferred.resolve(rootElement.children[0]);
62
+ }
63
+ };
64
+
65
+ parser.onerror = function(error) {
66
+ if (!finished) {
67
+ finished = true;
68
+ deferred.reject(error);
69
+ }
70
+ };
71
+
72
+ parser.write(xmlString).close();
73
+
74
+ return deferred.promise;
75
+ }
76
+
77
+ function mapObject(input, valueFunc, keyFunc) {
78
+ return _.reduce(input, function(result, value, key) {
79
+ var mappedKey = keyFunc(value, key, input);
80
+ result[mappedKey] = valueFunc(value, key, input);
81
+ return result;
82
+ }, {});
83
+ }
@@ -0,0 +1,61 @@
1
+ var _ = require("underscore");
2
+ var xmlbuilder = require("xmlbuilder");
3
+
4
+
5
+ exports.writeString = writeString;
6
+
7
+
8
+ function writeString(root, namespaces) {
9
+ var uriToPrefix = _.invert(namespaces);
10
+
11
+ var nodeWriters = {
12
+ element: writeElement,
13
+ text: writeTextNode
14
+ };
15
+
16
+ function writeNode(builder, node) {
17
+ return nodeWriters[node.type](builder, node);
18
+ }
19
+
20
+ function writeElement(builder, element) {
21
+ var elementBuilder = builder.element(mapElementName(element.name), element.attributes);
22
+ element.children.forEach(function(child) {
23
+ writeNode(elementBuilder, child);
24
+ });
25
+ }
26
+
27
+ function mapElementName(name) {
28
+ var longFormMatch = /^\{(.*)\}(.*)$/.exec(name);
29
+ if (longFormMatch) {
30
+ var prefix = uriToPrefix[longFormMatch[1]];
31
+ return prefix + (prefix === "" ? "" : ":") + longFormMatch[2];
32
+ } else {
33
+ return name;
34
+ }
35
+ }
36
+
37
+ function writeDocument(root) {
38
+ var builder = xmlbuilder
39
+ .create(mapElementName(root.name), {
40
+ version: '1.0',
41
+ encoding: 'UTF-8',
42
+ standalone: true
43
+ });
44
+
45
+ _.forEach(namespaces, function(uri, prefix) {
46
+ var key = "xmlns" + (prefix === "" ? "" : ":" + prefix);
47
+ builder.attribute(key, uri);
48
+ });
49
+
50
+ root.children.forEach(function(child) {
51
+ writeNode(builder, child);
52
+ });
53
+ return builder.end();
54
+ }
55
+
56
+ return writeDocument(root);
57
+ }
58
+
59
+ function writeTextNode(builder, node) {
60
+ builder.text(node.value);
61
+ }
package/lib/zipfile.js ADDED
@@ -0,0 +1,77 @@
1
+ var JSZip = require("jszip");
2
+
3
+ var promises = require("./promises");
4
+
5
+ exports.openArrayBuffer = openArrayBuffer;
6
+ exports.splitPath = splitPath;
7
+ exports.joinPath = joinPath;
8
+
9
+ function openArrayBuffer(arrayBuffer) {
10
+ var zipFile = new JSZip(arrayBuffer);
11
+ function exists(name) {
12
+ return zipFile.file(name) !== null;
13
+ }
14
+
15
+ function read(name, encoding) {
16
+ var array = zipFile.file(name).asUint8Array();
17
+ var buffer = uint8ArrayToBuffer(array);
18
+ if (encoding) {
19
+ return promises.when(buffer.toString(encoding));
20
+ } else {
21
+ return promises.when(buffer);
22
+ }
23
+ }
24
+
25
+ function write(name, contents) {
26
+ zipFile.file(name, contents);
27
+ }
28
+
29
+ function toBuffer() {
30
+ return zipFile.generate({type: "nodebuffer"});
31
+ }
32
+
33
+ return {
34
+ exists: exists,
35
+ read: read,
36
+ write: write,
37
+ toBuffer: toBuffer
38
+ };
39
+ }
40
+
41
+ function uint8ArrayToBuffer(array) {
42
+ if (Buffer.from && Buffer.from !== Uint8Array.from) {
43
+ return Buffer.from(array);
44
+ } else {
45
+ return new Buffer(array);
46
+ }
47
+ }
48
+
49
+ function splitPath(path) {
50
+ var lastIndex = path.lastIndexOf("/");
51
+ if (lastIndex === -1) {
52
+ return {dirname: "", basename: path};
53
+ } else {
54
+ return {
55
+ dirname: path.substring(0, lastIndex),
56
+ basename: path.substring(lastIndex + 1)
57
+ };
58
+ }
59
+ }
60
+
61
+ function joinPath() {
62
+ var nonEmptyPaths = Array.prototype.filter.call(arguments, function(path) {
63
+ return path;
64
+ });
65
+
66
+ var relevantPaths = [];
67
+
68
+ nonEmptyPaths.forEach(function(path) {
69
+ if (/^\//.test(path)) {
70
+ relevantPaths = [path];
71
+ } else {
72
+ relevantPaths.push(path);
73
+ }
74
+ });
75
+
76
+ return relevantPaths.join("/");
77
+ }