All new
alle new
This commit is contained in:
1 parent
da64472251
commit
5b20adee48
1605 files changed
+396517
-1674
No files matched your search
+78
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
|
||||
"rules": {
|
||||
"curly": ["error", "all"],
|
||||
"dot-location": ["error", "property"],
|
||||
"no-alert": ["error"],
|
||||
"no-caller": ["error"],
|
||||
"no-eval": ["error"],
|
||||
"no-extend-native": ["error"],
|
||||
"no-extra-bind": ["error"],
|
||||
"no-extra-label": ["error"],
|
||||
"no-floating-decimal": ["error"],
|
||||
"no-implicit-coercion": ["error", {"allow": ["!!"]}],
|
||||
"no-implied-eval": ["error"],
|
||||
"no-invalid-this": ["error"],
|
||||
"no-labels": ["error"],
|
||||
"no-lone-blocks": ["error"],
|
||||
"no-native-reassign": ["error"],
|
||||
"no-new": ["error"],
|
||||
"no-new-func": ["error"],
|
||||
"no-new-wrappers": ["error"],
|
||||
"no-return-assign": ["error"],
|
||||
"no-self-compare": ["error"],
|
||||
"no-sequences": ["error"],
|
||||
"no-throw-literal": ["error"],
|
||||
"no-unused-expressions": ["error"],
|
||||
"no-useless-call": ["error"],
|
||||
"no-with": ["error"],
|
||||
"no-unused-vars": ["error", {"args": "none"}],
|
||||
"object-curly-spacing": ["error", "never"],
|
||||
"radix": ["error", "always"],
|
||||
|
||||
"array-bracket-spacing": ["error", "never"],
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"camelcase": ["error"],
|
||||
"comma-spacing": ["error", {"before": false, "after": true}],
|
||||
"comma-style": ["error", "last"],
|
||||
"computed-property-spacing": ["error", "never"],
|
||||
"eol-last": ["error"],
|
||||
"indent": ["error", 4],
|
||||
"key-spacing": [
|
||||
"error",
|
||||
{"beforeColon": false, "afterColon": true, "mode": "strict"}
|
||||
],
|
||||
"keyword-spacing": ["error", {"before": true, "after": true}],
|
||||
"new-parens": ["error"],
|
||||
"no-array-constructor": ["error"],
|
||||
"no-continue": ["error"],
|
||||
"no-lonely-if": ["error"],
|
||||
"no-multiple-empty-lines": ["error", {"max": 2, "maxEOF": 1}],
|
||||
"no-new-object": ["error"],
|
||||
"no-spaced-func": ["error"],
|
||||
"no-trailing-spaces": ["error", {"skipBlankLines": true}],
|
||||
"no-whitespace-before-property": ["error"],
|
||||
"one-var": ["error", "never"],
|
||||
"semi": ["error", "always"],
|
||||
"semi-spacing": ["error", {"before": false}],
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"space-in-parens": ["error", "never"],
|
||||
"space-infix-ops": ["error"],
|
||||
"space-unary-ops": ["error", {"words": true, "nonwords": false}],
|
||||
"spaced-comment": ["error", "always"]
|
||||
},
|
||||
|
||||
"globals": {
|
||||
"Buffer": false,
|
||||
"console": true,
|
||||
"exports": true,
|
||||
"module": true,
|
||||
"process": false,
|
||||
"Promise": false,
|
||||
"require": false,
|
||||
"setTimeout": false,
|
||||
"TextDecoder": false,
|
||||
"Uint8Array": false
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
If you're reporting a bug or requesting a feature, please include:
|
||||
* a minimal example document
|
||||
* the HTML output that you'd expect
|
||||
|
||||
If you're reporting a bug, it's also useful to know what platform you're
|
||||
running on, including:
|
||||
|
||||
* the operating system and version
|
||||
* if running in the browser:
|
||||
* the browser and version
|
||||
* whether you get the same issue in other browsers
|
||||
* if running on node.js, the version you're using
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
In general, pull requests are not currently accepted.
|
||||
|
||||
Please instead submit an issue if you find a bug or would like to request a feature.
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: ["12", "14", "16"]
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- run: npm install
|
||||
|
||||
- run: npm test
|
||||
|
||||
- run: npm run check-typescript
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013, Michael Williamson
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+578
@@ -0,0 +1,578 @@
|
||||
# 1.13.0
|
||||
|
||||
* Replace the use of bluebird with native promises.
|
||||
|
||||
This is arguably a breaking change: however, the return value of functions
|
||||
such as convertToHtml() was only documented as a promise, rather than as a
|
||||
bluebird promise.
|
||||
|
||||
The only exception is that the docs previously used .done(): therefore,
|
||||
when returning promises from the API to external users, a done method is
|
||||
added.
|
||||
|
||||
Any callers that rely on bluebird promises can convert the returned promise
|
||||
into a bluebird promise using bluebird.Promise.resolve().
|
||||
|
||||
* Read the children of w:customXml elements.
|
||||
|
||||
* Handle paragraphs and runs that have been moved and tracked as a revision.
|
||||
|
||||
* Improve escaping in Markdown writer. This avoids some cases where source
|
||||
documents could inject arbitrary HTML into the converted document.
|
||||
|
||||
# 1.12.3
|
||||
|
||||
* Avoid excessive backtracking when parsing an unterminated string with many
|
||||
escape sequences. The previous behaviour would allow maliciously crafted
|
||||
documents to cause a denial of service.
|
||||
|
||||
Note that it is still strongly recommended to process untrusted documents in
|
||||
a separate thread with a timeout to avoid potential similar issues.
|
||||
|
||||
* Handle complex field separator and end characters without corresponding start
|
||||
characters.
|
||||
|
||||
# 1.12.2
|
||||
|
||||
* Avoid prototype pollution when reading the styles defined in a document. This
|
||||
avoids an issue where a maliciously crafted document could be used to set
|
||||
externalFileAccess to true.
|
||||
|
||||
# 1.12.1
|
||||
|
||||
* Fix: on Windows, when an image's content type includes a backslash in the
|
||||
subpart, files may be written outside of the directory set by --output-dir.
|
||||
|
||||
* Detect and ignore numbering levels that use numStyleLink to refer to
|
||||
themselves.
|
||||
|
||||
# 1.12.0
|
||||
|
||||
* Handle hyperlinked wp:anchor and wp:inline elements.
|
||||
|
||||
* Handle hyperlink complex fields with unquoted hrefs.
|
||||
|
||||
# 1.11.0
|
||||
|
||||
* Ignore style definitions using a style ID that has already been used.
|
||||
|
||||
* Disable external file accesses by default. External file access can be enabled
|
||||
using the externalFileAccess option.
|
||||
|
||||
* Handle numbering levels defined without an index.
|
||||
|
||||
# 1.10.0
|
||||
|
||||
* Add "Heading" and "Body" styles, as found in documents created by Apple Pages,
|
||||
to the default style map.
|
||||
|
||||
* Handle structured document tags representing checkboxes wrapped in other
|
||||
elements, such as table cells. Previously, the wrapping elements would have
|
||||
been ignored.
|
||||
|
||||
* Ignore deleted table rows.
|
||||
|
||||
* Add notes on security.
|
||||
|
||||
* Revert the change to explicitly use commonjs modules. This appeared to cause
|
||||
issues with some bundlers such as webpack when using mammoth.browser.js.
|
||||
|
||||
# 1.9.1
|
||||
|
||||
* Ignore AlternateContent elements when there is no Fallback element.
|
||||
|
||||
* Explicitly use commonjs modules. Since the modules should have previously been
|
||||
implicitly treated as commonjs modules, this shouldn't affect behaviour.
|
||||
|
||||
# 1.9.0
|
||||
|
||||
* Update lop to 0.4.2, which removes the use of the util module when there are
|
||||
errors during parsing. This should remove the need to polyfill util in the
|
||||
browser.
|
||||
|
||||
* Detect checkboxes, both as complex fields and structured document tags, and
|
||||
convert them to checkbox inputs.
|
||||
|
||||
# 1.8.0
|
||||
|
||||
* Add style mapping for highlights.
|
||||
|
||||
# 1.7.2
|
||||
|
||||
* Remove the use of the path module from common code used by browser builds.
|
||||
This should remove the need to polyfill path in the browser.
|
||||
|
||||
# 1.7.1
|
||||
|
||||
* Handle numbering level definitions without an explicit format.
|
||||
|
||||
* Switch the precedence of numbering properties in paragraph properties and the
|
||||
numbering in paragraph styles so that the numbering properties in paragraph
|
||||
properties takes precedence.
|
||||
|
||||
# 1.7.0
|
||||
|
||||
* Support attributes in HTML paths in style mappings.
|
||||
|
||||
* Improve error message when failing to find the body element in a document.
|
||||
|
||||
* Add support for the strict document format.
|
||||
|
||||
# 1.6.0
|
||||
|
||||
* Add transformDocument to the TypeScript declarations.
|
||||
|
||||
* Support merged paragraphs when revisions are tracked.
|
||||
|
||||
* Use xmldom instead of sax to parse XML documents. This should remove the need
|
||||
to polyfill stream in the browser.
|
||||
|
||||
* Adjust the internal implementation to remove the use of Buffer on the critical
|
||||
path, and provide APIs to read images and documents with embedded style maps
|
||||
without using Buffer. This should remove the need to polyfill Buffer in the
|
||||
browser. Since TextDecoder is now used, the minimum version of node.js is now
|
||||
v12.
|
||||
|
||||
* Remove the use of the util module. This should remove the need to polyfill
|
||||
util in the browser.
|
||||
|
||||
# 1.5.1
|
||||
|
||||
* Fix: npm 7 changed the behaviour of prepublish, causing the browser build not
|
||||
to be updated before publishing to npm. We now use prepare instead of
|
||||
prepublish, which has the same behaviour that prepublish previously had.
|
||||
|
||||
# 1.5.0
|
||||
|
||||
* Only use the alt text of image elements as a fallback. If an alt attribute is
|
||||
returned from the function passed to mammoth.images.imgElement, that value
|
||||
will now be preferred to the alt text of the image element.
|
||||
|
||||
# 1.4.21
|
||||
|
||||
* Ignore w:u elements when w:val is missing.
|
||||
|
||||
# 1.4.20
|
||||
|
||||
* Emit warning instead of throwing exception when image file cannot be found for
|
||||
a:blip elements.
|
||||
|
||||
# 1.4.19
|
||||
|
||||
* Add TypeScript declarations.
|
||||
|
||||
# 1.4.18
|
||||
|
||||
* When extracting raw text, convert tab elements to tab characters.
|
||||
|
||||
* Handle internal hyperlinks created with complex fields.
|
||||
|
||||
* Update JSZip to 3.2.0. This addresses CVE-2021-23413 in JSZip.
|
||||
|
||||
# 1.4.17
|
||||
|
||||
* Handle w:num with invalid w:abstractNumId.
|
||||
* Update underscore to 1.13.1.
|
||||
|
||||
# 1.4.16
|
||||
|
||||
* Convert symbols in supported fonts to corresponding Unicode characters.
|
||||
|
||||
# 1.4.15
|
||||
|
||||
* Support numbering defined by paragraph style.
|
||||
|
||||
# 1.4.14
|
||||
|
||||
* Add style mapping for all caps.
|
||||
|
||||
# 1.4.13
|
||||
|
||||
* Use package-lock.json instead of npm-shrinkwrap.json.
|
||||
|
||||
# 1.4.12
|
||||
|
||||
* Handle underline elements where w:val is "none".
|
||||
|
||||
# 1.4.11
|
||||
|
||||
* Re-publishing to remove superfluous files.
|
||||
|
||||
# 1.4.10
|
||||
|
||||
* Read font size for runs.
|
||||
* Support soft hyphens.
|
||||
|
||||
# 1.4.9
|
||||
|
||||
* Allow hyperlinks to be collapsed.
|
||||
|
||||
# 1.4.8
|
||||
|
||||
* Improve list support by following w:numStyleLink in w:abstractNum.
|
||||
|
||||
# 1.4.7
|
||||
|
||||
* Update xmlbuilder dependency.
|
||||
|
||||
# 1.4.6
|
||||
|
||||
* Fix: default style mappings caused footnotes, endnotes and comments
|
||||
containing multiple paragraphs to be converted into a single paragraph.
|
||||
|
||||
# 1.4.5
|
||||
|
||||
* When using the pretty HTML writer, don't format HTML inside pre elements.
|
||||
|
||||
* Read the children of v:rect elements.
|
||||
|
||||
# 1.4.4
|
||||
|
||||
* Parse paragraph indents.
|
||||
|
||||
* Read part paths using relationships. This improves support for documents
|
||||
created by Word Online.
|
||||
|
||||
# 1.4.3
|
||||
|
||||
* Add style mapping for small caps.
|
||||
|
||||
* Add style mapping for tables.
|
||||
|
||||
# 1.4.2
|
||||
|
||||
* Read children of v:group elements.
|
||||
|
||||
# 1.4.1
|
||||
|
||||
* Read w:noBreakHyphen elements as non-breaking hyphen characters.
|
||||
|
||||
# 1.4.0
|
||||
|
||||
* Extract the default data URI image converter to the images module.
|
||||
|
||||
* Add anchor on hyperlinks as fragment if present.
|
||||
|
||||
* Convert target frames on hyperlinks to targets on anchors.
|
||||
|
||||
* Detect header rows in tables and convert to thead > tr > th.
|
||||
|
||||
# 1.3.6
|
||||
|
||||
* Handle complex fields that do not have a "separate" fldChar.
|
||||
|
||||
# 1.3.5
|
||||
|
||||
* Add transforms.run.
|
||||
|
||||
# 1.3.4
|
||||
|
||||
* Read children of w:object elements.
|
||||
|
||||
# 1.3.3
|
||||
|
||||
* Handle hyperlinks created with complex fields.
|
||||
|
||||
# 1.3.2
|
||||
|
||||
* Handle absolute paths within zip files. This should fix an issue where some
|
||||
images within a document couldn't be found.
|
||||
|
||||
# 1.3.1
|
||||
|
||||
* Add getDescendants() and getDescendantsOfType() to transforms module.
|
||||
|
||||
* Add font property to runs.
|
||||
|
||||
# 1.3.0
|
||||
|
||||
* Allow style names to be mapped by prefix. For instance:
|
||||
|
||||
r[style-name^='Code '] => code
|
||||
|
||||
* Add default style mappings for Heading 5 and Heading 6.
|
||||
|
||||
* Allow escape sequences in style IDs, style names and CSS class names.
|
||||
|
||||
* Allow a separator to be specified when HTML elements are collapsed.
|
||||
|
||||
* Add includeEmbeddedStyleMap option to allow embedded style maps to be
|
||||
disabled.
|
||||
|
||||
* Include embedded styles when explicit style map is passed.
|
||||
|
||||
# 1.2.5
|
||||
|
||||
* Ignore bold, italic, underline and strikethrough elements that have a value of
|
||||
false or 0.
|
||||
|
||||
# 1.2.4
|
||||
|
||||
* Ignore v:imagedata elements without relationship ID with warning.
|
||||
|
||||
# 1.2.3
|
||||
|
||||
* Expect end token when parsing style mappings. This causes warnings to be
|
||||
emitted instead of silenting ignoring unparsed tokens.
|
||||
|
||||
# 1.2.2
|
||||
|
||||
* Ignore blank lines in style map.
|
||||
|
||||
# 1.2.1
|
||||
|
||||
* Use alt text title as alt text for images when the alt text description is
|
||||
missing entirely.
|
||||
|
||||
# 1.2.0
|
||||
|
||||
* Throw more informative error when word/document.xml cannot be found.
|
||||
|
||||
* Generate messages of type "error" instead of "warning" when image conversion
|
||||
throws an exception.
|
||||
|
||||
* Use alt text title as alt text for images when the alt text description is
|
||||
blank.
|
||||
|
||||
# 1.1.0
|
||||
|
||||
* Add support for comments.
|
||||
|
||||
# 1.0.4
|
||||
|
||||
* Add support for w:sdt elements. This allows the bodies of content controls,
|
||||
such as bibliographies, to be converted.
|
||||
|
||||
* Avoid stack overflows when elements have many children.
|
||||
|
||||
# 1.0.3
|
||||
|
||||
* Actually add support for table cells spanning multiple rows.
|
||||
|
||||
# 1.0.2
|
||||
|
||||
* Add support for table cells spanning multiple rows.
|
||||
|
||||
# 1.0.1
|
||||
|
||||
* Add support for table cells spanning multiple columns.
|
||||
|
||||
# 1.0.0
|
||||
|
||||
* Remove deprecated convertUnderline option.
|
||||
|
||||
* Officially support ID prefixes.
|
||||
|
||||
* Generated IDs no longer insert a hyphen after the ID prefix.
|
||||
|
||||
* The default ID prefix is now the empty string rather than a random number
|
||||
followed by a hyphen.
|
||||
|
||||
* Rename mammoth.images.inline to mammoth.images.imgElement to better reflect
|
||||
its behaviour.
|
||||
|
||||
# 0.3.33
|
||||
|
||||
* Improve collapsing of elements when there are empty elements.
|
||||
|
||||
# 0.3.32
|
||||
|
||||
* Allow bold and italic style mappings to be configured.
|
||||
|
||||
# 0.3.31
|
||||
|
||||
* Handle references to missing styles when reading documents.
|
||||
|
||||
# 0.3.30
|
||||
|
||||
* Improve support for lists made in LibreOffice. Specifically, this changes the
|
||||
default style mapping for paragraphs with a style of "Normal" to have the
|
||||
lowest precedence.
|
||||
|
||||
* Replace nomnom with argparse for CLI argument parsing. Usage should be the
|
||||
same, but unknown arguments will now cause an exit with failure rather than
|
||||
being ignored.
|
||||
|
||||
# 0.3.29
|
||||
|
||||
* Always use mc:Fallback when reading mc:AlternateContent elements.
|
||||
|
||||
# 0.3.28
|
||||
|
||||
* Avoid inserting newlines around inline elements when pretty-printing HTML.
|
||||
|
||||
* CLI: print warnings to stderr.
|
||||
|
||||
* Read v:imagedata with r:id attribute.
|
||||
|
||||
* Read children of v:roundrect.
|
||||
|
||||
* Ignore office-word:wrap, v:shadow and v:shapetype.
|
||||
|
||||
# 0.3.27
|
||||
|
||||
* Fix recursive collapsing of elements.
|
||||
|
||||
# 0.3.26
|
||||
|
||||
* Keep HTML elements open correctly when the HTML path contains no fresh
|
||||
element.
|
||||
|
||||
* Collapse similar non-fresh HTML elements together.
|
||||
|
||||
* Don't escape double quotes outside of attributes.
|
||||
|
||||
# 0.3.25
|
||||
|
||||
* When the styleMap option is passed as a string, ignore lines beginning with #.
|
||||
|
||||
* Generate warnings for not-understood style mappings and continue, rather than
|
||||
stopping with an error.
|
||||
|
||||
* Add support for embedded style maps.
|
||||
|
||||
* Add support for linked images.
|
||||
|
||||
# 0.3.24
|
||||
|
||||
* Ignore w:numPr elements without w:numId or w:ilvl children.
|
||||
|
||||
# 0.3.23
|
||||
|
||||
* Support links and images in footnotes and endnotes.
|
||||
|
||||
# 0.3.22
|
||||
|
||||
* Add support for underlines in style map.
|
||||
|
||||
* Add support for strikethrough.
|
||||
|
||||
# 0.3.21
|
||||
|
||||
* Add basic support for text boxes. The contents of the text box are treated as
|
||||
a separate paragraph that appears after the paragraph containing the text box.
|
||||
|
||||
# 0.3.20
|
||||
|
||||
* Support styles defined without a name
|
||||
|
||||
# 0.3.19
|
||||
|
||||
* Add ignoreEmptyParagraphs option, which defaults to true.
|
||||
|
||||
# 0.3.18
|
||||
|
||||
* Make style names case-insensitive in style mappings. This should make style
|
||||
mappings easier to write, especially since Microsoft Word sometimes represents
|
||||
style names in the UI differently from in the style definition. For instance,
|
||||
the style displayed in Word as "Heading 1" has a style name of "heading 1".
|
||||
This hopefully shouldn't cause an issue for anyone, but if you were relying
|
||||
on case-sensitivity, please do get in touch.
|
||||
|
||||
# 0.3.17
|
||||
|
||||
* Add support for hyperlinks to bookmarks in the same document.
|
||||
|
||||
# 0.3.16
|
||||
|
||||
* Add basic support for Markdown. Not all features are currently supported.
|
||||
|
||||
# 0.3.15
|
||||
|
||||
* Add default style mappings for builtin footnote and endnote styles in
|
||||
Microsoft Word and LibreOffice.
|
||||
|
||||
* Allow style mappings with a zero-element HTML path.
|
||||
|
||||
* Emit warnings when image types are unlikely to be supported by web browsers.
|
||||
|
||||
* Update jszip to 2.4.0.
|
||||
|
||||
# 0.3.14
|
||||
|
||||
* Add support for endnotes.
|
||||
|
||||
# 0.3.13
|
||||
|
||||
* Update license-sniffer to fix license comments in mammoth.browser.js.
|
||||
|
||||
# 0.3.12
|
||||
|
||||
* Update bluebird dependency to fix operation in web workers.
|
||||
|
||||
# 0.3.11
|
||||
|
||||
* Add support for superscript and subscript text.
|
||||
|
||||
# 0.3.10
|
||||
|
||||
* Handle XML files that start with the UTF-8 BOM.
|
||||
|
||||
# 0.3.9
|
||||
|
||||
* Add support for reading Buffer instances.
|
||||
|
||||
# 0.3.8
|
||||
|
||||
* Add support for line breaks.
|
||||
|
||||
# 0.3.7
|
||||
|
||||
* Add optional underline conversion.
|
||||
|
||||
# 0.3.6
|
||||
|
||||
* Add helper `mammoth.transforms.paragraph`.
|
||||
|
||||
# 0.3.5
|
||||
|
||||
* Add `mammoth.images.inline`, and document custom image conversion.
|
||||
|
||||
# 0.3.4
|
||||
|
||||
* Add the function `mammoth.extractRawText`.
|
||||
|
||||
# 0.3.3
|
||||
|
||||
* Add support for tables.
|
||||
|
||||
# 0.3.2
|
||||
|
||||
* Add support for footnotes.
|
||||
|
||||
# 0.3.1
|
||||
|
||||
* Fix: all non-paragraph styles are treated as character styles when reading
|
||||
styles from a docx document.
|
||||
|
||||
# 0.3.0
|
||||
|
||||
* Rename the existing concept of a "style name" to "style ID" to correspond to
|
||||
the terminology used internally in docx files. Any document transforms that
|
||||
get or set the `styleName` property should use the `styleId` property instead.
|
||||
|
||||
* Allow paragraphs and runs to be matched by style name. For instance, to match
|
||||
a paragraph with the style name `Heading 1`:
|
||||
|
||||
p[style-name='Heading 1']
|
||||
|
||||
* Use an array of raw strings rather than an array of style mappings when
|
||||
passing options to convertToHtml.
|
||||
|
||||
* Add support for node 0.11 (tested on node 0.11.13).
|
||||
|
||||
# 0.2.2
|
||||
|
||||
* Fix: exception when bad arguments are passed into unzip.openZip.
|
||||
|
||||
# 0.2.1
|
||||
|
||||
* Fix browserify support.
|
||||
|
||||
# 0.2.0
|
||||
|
||||
* Rename mammoth.style() to mammoth.styleMapping().
|
||||
|
||||
* Add support for document transforms.
|
||||
|
||||
* Append standard style map to custom style map unless explicitly excluded.
|
||||
+1016
File diff suppressed because it is too large.
Load diff
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var ArgumentParser = require("argparse").ArgumentParser;
|
||||
var main = require("../lib/main");
|
||||
|
||||
var parser = new ArgumentParser({
|
||||
addHelp: true
|
||||
});
|
||||
|
||||
parser.addArgument(["docx-path"], {
|
||||
type: "string",
|
||||
help: "Path to the .docx file to convert."
|
||||
});
|
||||
|
||||
var outputGroup = parser.addMutuallyExclusiveGroup();
|
||||
outputGroup.addArgument(["output-path"], {
|
||||
type: "string",
|
||||
nargs: "?",
|
||||
help: "Output path for the generated document. Images will be stored inline in the output document. Output is written to stdout if not set."
|
||||
});
|
||||
outputGroup.addArgument(["--output-dir"], {
|
||||
type: "string",
|
||||
help: "Output directory for generated HTML and images. Images will be stored in separate files. Mutually exclusive with output-path."
|
||||
});
|
||||
|
||||
parser.addArgument(["--output-format"], {
|
||||
defaultValue: "html",
|
||||
choices: ["html", "markdown"],
|
||||
help: "Output format."
|
||||
});
|
||||
|
||||
parser.addArgument(["--style-map"], {
|
||||
type: "string",
|
||||
help: "File containg a style map."
|
||||
});
|
||||
|
||||
|
||||
main(parser.parseArgs());
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
var promises = require("../../lib/promises");
|
||||
|
||||
exports.Files = Files;
|
||||
|
||||
|
||||
function Files() {
|
||||
function read(uri) {
|
||||
return promises.reject(new Error("could not open external image: '" + uri + "'\ncannot open linked files from a web browser"));
|
||||
}
|
||||
|
||||
return {
|
||||
read: read
|
||||
};
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
var promises = require("../lib/promises");
|
||||
var zipfile = require("../lib/zipfile");
|
||||
|
||||
exports.openZip = openZip;
|
||||
|
||||
function openZip(options) {
|
||||
if (options.arrayBuffer) {
|
||||
return promises.resolve(zipfile.openArrayBuffer(options.arrayBuffer));
|
||||
} else {
|
||||
return promises.reject(new Error("Could not find file in options"));
|
||||
}
|
||||
}
|
||||
+469
@@ -0,0 +1,469 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
var promises = require("./promises");
|
||||
var documents = require("./documents");
|
||||
var htmlPaths = require("./styles/html-paths");
|
||||
var results = require("./results");
|
||||
var images = require("./images");
|
||||
var Html = require("./html");
|
||||
var writers = require("./writers");
|
||||
|
||||
exports.DocumentConverter = DocumentConverter;
|
||||
|
||||
|
||||
function DocumentConverter(options) {
|
||||
return {
|
||||
convertToHtml: function(element) {
|
||||
var comments = _.indexBy(
|
||||
element.type === documents.types.document ? element.comments : [],
|
||||
"commentId"
|
||||
);
|
||||
var conversion = new DocumentConversion(options, comments);
|
||||
return conversion.convertToHtml(element);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function DocumentConversion(options, comments) {
|
||||
var noteNumber = 1;
|
||||
|
||||
var noteReferences = [];
|
||||
|
||||
var referencedComments = [];
|
||||
|
||||
options = _.extend({ignoreEmptyParagraphs: true}, options);
|
||||
var idPrefix = options.idPrefix === undefined ? "" : options.idPrefix;
|
||||
var ignoreEmptyParagraphs = options.ignoreEmptyParagraphs;
|
||||
|
||||
var defaultParagraphStyle = htmlPaths.topLevelElement("p");
|
||||
|
||||
var styleMap = options.styleMap || [];
|
||||
|
||||
function convertToHtml(document) {
|
||||
var messages = [];
|
||||
|
||||
var html = elementToHtml(document, messages, Object.create(null));
|
||||
|
||||
var deferredNodes = [];
|
||||
walkHtml(html, function(node) {
|
||||
if (node.type === "deferred") {
|
||||
deferredNodes.push(node);
|
||||
}
|
||||
});
|
||||
var deferredValues = Object.create(null);
|
||||
return promises.forEachSeries(deferredNodes, function(deferred) {
|
||||
return deferred.value().then(function(value) {
|
||||
deferredValues[deferred.id] = value;
|
||||
});
|
||||
}).then(function() {
|
||||
function replaceDeferred(nodes) {
|
||||
return flatMap(nodes, function(node) {
|
||||
if (node.type === "deferred") {
|
||||
return deferredValues[node.id];
|
||||
} else if (node.children) {
|
||||
return [
|
||||
_.extend({}, node, {
|
||||
children: replaceDeferred(node.children)
|
||||
})
|
||||
];
|
||||
} else {
|
||||
return [node];
|
||||
}
|
||||
});
|
||||
}
|
||||
var writer = writers.writer({
|
||||
prettyPrint: options.prettyPrint,
|
||||
outputFormat: options.outputFormat
|
||||
});
|
||||
Html.write(writer, Html.simplify(replaceDeferred(html)));
|
||||
return new results.Result(writer.asString(), messages);
|
||||
});
|
||||
}
|
||||
|
||||
function convertElements(elements, messages, context) {
|
||||
return flatMap(elements, function(element) {
|
||||
return elementToHtml(element, messages, context);
|
||||
});
|
||||
}
|
||||
|
||||
function elementToHtml(element, messages, context) {
|
||||
if (!context) {
|
||||
throw new Error("context not set");
|
||||
}
|
||||
var handler = elementConverters[element.type];
|
||||
if (handler) {
|
||||
return handler(element, messages, context);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function convertParagraph(element, messages, context) {
|
||||
return htmlPathForParagraph(element, messages).wrap(function() {
|
||||
var content = convertElements(element.children, messages, context);
|
||||
if (ignoreEmptyParagraphs) {
|
||||
return content;
|
||||
} else {
|
||||
return [Html.forceWrite].concat(content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function htmlPathForParagraph(element, messages) {
|
||||
var style = findStyle(element);
|
||||
|
||||
if (style) {
|
||||
return style.to;
|
||||
} else {
|
||||
if (element.styleId) {
|
||||
messages.push(unrecognisedStyleWarning("paragraph", element));
|
||||
}
|
||||
return defaultParagraphStyle;
|
||||
}
|
||||
}
|
||||
|
||||
function convertRun(run, messages, context) {
|
||||
var nodes = function() {
|
||||
return convertElements(run.children, messages, context);
|
||||
};
|
||||
var paths = [];
|
||||
if (run.highlight !== null) {
|
||||
var path = findHtmlPath({type: "highlight", color: run.highlight});
|
||||
if (path) {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
if (run.isSmallCaps) {
|
||||
paths.push(findHtmlPathForRunProperty("smallCaps"));
|
||||
}
|
||||
if (run.isAllCaps) {
|
||||
paths.push(findHtmlPathForRunProperty("allCaps"));
|
||||
}
|
||||
if (run.isStrikethrough) {
|
||||
paths.push(findHtmlPathForRunProperty("strikethrough", "s"));
|
||||
}
|
||||
if (run.isUnderline) {
|
||||
paths.push(findHtmlPathForRunProperty("underline"));
|
||||
}
|
||||
if (run.verticalAlignment === documents.verticalAlignment.subscript) {
|
||||
paths.push(htmlPaths.element("sub", {}, {fresh: false}));
|
||||
}
|
||||
if (run.verticalAlignment === documents.verticalAlignment.superscript) {
|
||||
paths.push(htmlPaths.element("sup", {}, {fresh: false}));
|
||||
}
|
||||
if (run.isItalic) {
|
||||
paths.push(findHtmlPathForRunProperty("italic", "em"));
|
||||
}
|
||||
if (run.isBold) {
|
||||
paths.push(findHtmlPathForRunProperty("bold", "strong"));
|
||||
}
|
||||
var stylePath = htmlPaths.empty;
|
||||
var style = findStyle(run);
|
||||
if (style) {
|
||||
stylePath = style.to;
|
||||
} else if (run.styleId) {
|
||||
messages.push(unrecognisedStyleWarning("run", run));
|
||||
}
|
||||
paths.push(stylePath);
|
||||
|
||||
paths.forEach(function(path) {
|
||||
nodes = path.wrap.bind(path, nodes);
|
||||
});
|
||||
|
||||
return nodes();
|
||||
}
|
||||
|
||||
function findHtmlPathForRunProperty(elementType, defaultTagName) {
|
||||
var path = findHtmlPath({type: elementType});
|
||||
if (path) {
|
||||
return path;
|
||||
} else if (defaultTagName) {
|
||||
return htmlPaths.element(defaultTagName, {}, {fresh: false});
|
||||
} else {
|
||||
return htmlPaths.empty;
|
||||
}
|
||||
}
|
||||
|
||||
function findHtmlPath(element, defaultPath) {
|
||||
var style = findStyle(element);
|
||||
return style ? style.to : defaultPath;
|
||||
}
|
||||
|
||||
function findStyle(element) {
|
||||
for (var i = 0; i < styleMap.length; i++) {
|
||||
if (styleMap[i].from.matches(element)) {
|
||||
return styleMap[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function recoveringConvertImage(convertImage) {
|
||||
return function(image, messages) {
|
||||
return promises.try(function() {
|
||||
return convertImage(image, messages);
|
||||
}).catch(function(error) {
|
||||
messages.push(results.error(error));
|
||||
return [];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function noteHtmlId(note) {
|
||||
return referentHtmlId(note.noteType, note.noteId);
|
||||
}
|
||||
|
||||
function noteRefHtmlId(note) {
|
||||
return referenceHtmlId(note.noteType, note.noteId);
|
||||
}
|
||||
|
||||
function referentHtmlId(referenceType, referenceId) {
|
||||
return htmlId(referenceType + "-" + referenceId);
|
||||
}
|
||||
|
||||
function referenceHtmlId(referenceType, referenceId) {
|
||||
return htmlId(referenceType + "-ref-" + referenceId);
|
||||
}
|
||||
|
||||
function htmlId(suffix) {
|
||||
return idPrefix + suffix;
|
||||
}
|
||||
|
||||
var defaultTablePath = htmlPaths.elements([
|
||||
htmlPaths.element("table", {}, {fresh: true})
|
||||
]);
|
||||
|
||||
function convertTable(element, messages, context) {
|
||||
return findHtmlPath(element, defaultTablePath).wrap(function() {
|
||||
return convertTableChildren(element, messages, context);
|
||||
});
|
||||
}
|
||||
|
||||
function convertTableChildren(element, messages, context) {
|
||||
var bodyIndex = _.findIndex(element.children, function(child) {
|
||||
return !child.type === documents.types.tableRow || !child.isHeader;
|
||||
});
|
||||
if (bodyIndex === -1) {
|
||||
bodyIndex = element.children.length;
|
||||
}
|
||||
var children;
|
||||
if (bodyIndex === 0) {
|
||||
children = convertElements(
|
||||
element.children,
|
||||
messages,
|
||||
_.extend({}, context, {isTableHeader: false})
|
||||
);
|
||||
} else {
|
||||
var headRows = convertElements(
|
||||
element.children.slice(0, bodyIndex),
|
||||
messages,
|
||||
_.extend({}, context, {isTableHeader: true})
|
||||
);
|
||||
var bodyRows = convertElements(
|
||||
element.children.slice(bodyIndex),
|
||||
messages,
|
||||
_.extend({}, context, {isTableHeader: false})
|
||||
);
|
||||
children = [
|
||||
Html.freshElement("thead", {}, headRows),
|
||||
Html.freshElement("tbody", {}, bodyRows)
|
||||
];
|
||||
}
|
||||
return [Html.forceWrite].concat(children);
|
||||
}
|
||||
|
||||
function convertTableRow(element, messages, context) {
|
||||
var children = convertElements(element.children, messages, context);
|
||||
return [
|
||||
Html.freshElement("tr", {}, [Html.forceWrite].concat(children))
|
||||
];
|
||||
}
|
||||
|
||||
function convertTableCell(element, messages, context) {
|
||||
var tagName = context.isTableHeader ? "th" : "td";
|
||||
var children = convertElements(element.children, messages, context);
|
||||
var attributes = {};
|
||||
if (element.colSpan !== 1) {
|
||||
attributes.colspan = element.colSpan.toString();
|
||||
}
|
||||
if (element.rowSpan !== 1) {
|
||||
attributes.rowspan = element.rowSpan.toString();
|
||||
}
|
||||
|
||||
return [
|
||||
Html.freshElement(tagName, attributes, [Html.forceWrite].concat(children))
|
||||
];
|
||||
}
|
||||
|
||||
function convertCommentReference(reference, messages, context) {
|
||||
return findHtmlPath(reference, htmlPaths.ignore).wrap(function() {
|
||||
var comment = comments[reference.commentId];
|
||||
var count = referencedComments.length + 1;
|
||||
var label = "[" + commentAuthorLabel(comment) + count + "]";
|
||||
referencedComments.push({label: label, comment: comment});
|
||||
// TODO: remove duplication with note references
|
||||
return [
|
||||
Html.freshElement("a", {
|
||||
href: "#" + referentHtmlId("comment", reference.commentId),
|
||||
id: referenceHtmlId("comment", reference.commentId)
|
||||
}, [Html.text(label)])
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
function convertComment(referencedComment, messages, context) {
|
||||
// TODO: remove duplication with note references
|
||||
|
||||
var label = referencedComment.label;
|
||||
var comment = referencedComment.comment;
|
||||
var body = convertElements(comment.body, messages, context).concat([
|
||||
Html.nonFreshElement("p", {}, [
|
||||
Html.text(" "),
|
||||
Html.freshElement("a", {"href": "#" + referenceHtmlId("comment", comment.commentId)}, [
|
||||
Html.text("↑")
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
return [
|
||||
Html.freshElement(
|
||||
"dt",
|
||||
{"id": referentHtmlId("comment", comment.commentId)},
|
||||
[Html.text("Comment " + label)]
|
||||
),
|
||||
Html.freshElement("dd", {}, body)
|
||||
];
|
||||
}
|
||||
|
||||
function convertBreak(element, messages, context) {
|
||||
return htmlPathForBreak(element).wrap(function() {
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
function htmlPathForBreak(element) {
|
||||
var style = findStyle(element);
|
||||
if (style) {
|
||||
return style.to;
|
||||
} else if (element.breakType === "line") {
|
||||
return htmlPaths.topLevelElement("br");
|
||||
} else {
|
||||
return htmlPaths.empty;
|
||||
}
|
||||
}
|
||||
|
||||
var elementConverters = {
|
||||
"document": function(document, messages, context) {
|
||||
var children = convertElements(document.children, messages, context);
|
||||
var notes = noteReferences.map(function(noteReference) {
|
||||
return document.notes.resolve(noteReference);
|
||||
});
|
||||
var notesNodes = convertElements(notes, messages, context);
|
||||
return children.concat([
|
||||
Html.freshElement("ol", {}, notesNodes),
|
||||
Html.freshElement("dl", {}, flatMap(referencedComments, function(referencedComment) {
|
||||
return convertComment(referencedComment, messages, context);
|
||||
}))
|
||||
]);
|
||||
},
|
||||
"paragraph": convertParagraph,
|
||||
"run": convertRun,
|
||||
"text": function(element, messages, context) {
|
||||
return [Html.text(element.value)];
|
||||
},
|
||||
"tab": function(element, messages, context) {
|
||||
return [Html.text("\t")];
|
||||
},
|
||||
"hyperlink": function(element, messages, context) {
|
||||
var href = element.anchor ? "#" + htmlId(element.anchor) : element.href;
|
||||
var attributes = {href: href};
|
||||
if (element.targetFrame != null) {
|
||||
attributes.target = element.targetFrame;
|
||||
}
|
||||
|
||||
var children = convertElements(element.children, messages, context);
|
||||
return [Html.nonFreshElement("a", attributes, children)];
|
||||
},
|
||||
"checkbox": function(element) {
|
||||
var attributes = {type: "checkbox"};
|
||||
if (element.checked) {
|
||||
attributes["checked"] = "checked";
|
||||
}
|
||||
return [Html.freshElement("input", attributes)];
|
||||
},
|
||||
"bookmarkStart": function(element, messages, context) {
|
||||
var anchor = Html.freshElement("a", {
|
||||
id: htmlId(element.name)
|
||||
}, [Html.forceWrite]);
|
||||
return [anchor];
|
||||
},
|
||||
"noteReference": function(element, messages, context) {
|
||||
noteReferences.push(element);
|
||||
var anchor = Html.freshElement("a", {
|
||||
href: "#" + noteHtmlId(element),
|
||||
id: noteRefHtmlId(element)
|
||||
}, [Html.text("[" + (noteNumber++) + "]")]);
|
||||
|
||||
return [Html.freshElement("sup", {}, [anchor])];
|
||||
},
|
||||
"note": function(element, messages, context) {
|
||||
var children = convertElements(element.body, messages, context);
|
||||
var backLink = Html.elementWithTag(htmlPaths.element("p", {}, {fresh: false}), [
|
||||
Html.text(" "),
|
||||
Html.freshElement("a", {href: "#" + noteRefHtmlId(element)}, [Html.text("↑")])
|
||||
]);
|
||||
var body = children.concat([backLink]);
|
||||
|
||||
return Html.freshElement("li", {id: noteHtmlId(element)}, body);
|
||||
},
|
||||
"commentReference": convertCommentReference,
|
||||
"comment": convertComment,
|
||||
"image": deferredConversion(recoveringConvertImage(options.convertImage || images.dataUri)),
|
||||
"table": convertTable,
|
||||
"tableRow": convertTableRow,
|
||||
"tableCell": convertTableCell,
|
||||
"break": convertBreak
|
||||
};
|
||||
return {
|
||||
convertToHtml: convertToHtml
|
||||
};
|
||||
}
|
||||
|
||||
var deferredId = 1;
|
||||
|
||||
function deferredConversion(func) {
|
||||
return function(element, messages, context) {
|
||||
return [
|
||||
{
|
||||
type: "deferred",
|
||||
id: deferredId++,
|
||||
value: function() {
|
||||
return func(element, messages, context);
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
function unrecognisedStyleWarning(type, element) {
|
||||
return results.warning(
|
||||
"Unrecognised " + type + " style: '" + element.styleName + "'" +
|
||||
" (Style ID: " + element.styleId + ")"
|
||||
);
|
||||
}
|
||||
|
||||
function flatMap(values, func) {
|
||||
return _.flatten(values.map(func), true);
|
||||
}
|
||||
|
||||
function walkHtml(nodes, callback) {
|
||||
nodes.forEach(function(node) {
|
||||
callback(node);
|
||||
if (node.children) {
|
||||
walkHtml(node.children, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var commentAuthorLabel = exports.commentAuthorLabel = function commentAuthorLabel(comment) {
|
||||
return comment.authorInitials || "";
|
||||
};
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
var types = exports.types = {
|
||||
document: "document",
|
||||
paragraph: "paragraph",
|
||||
run: "run",
|
||||
text: "text",
|
||||
tab: "tab",
|
||||
checkbox: "checkbox",
|
||||
hyperlink: "hyperlink",
|
||||
noteReference: "noteReference",
|
||||
image: "image",
|
||||
note: "note",
|
||||
commentReference: "commentReference",
|
||||
comment: "comment",
|
||||
table: "table",
|
||||
tableRow: "tableRow",
|
||||
tableCell: "tableCell",
|
||||
"break": "break",
|
||||
bookmarkStart: "bookmarkStart"
|
||||
};
|
||||
|
||||
function Document(children, options) {
|
||||
options = options || {};
|
||||
return {
|
||||
type: types.document,
|
||||
children: children,
|
||||
notes: options.notes || new Notes({}),
|
||||
comments: options.comments || []
|
||||
};
|
||||
}
|
||||
|
||||
function Paragraph(children, properties) {
|
||||
properties = properties || {};
|
||||
var indent = properties.indent || {};
|
||||
return {
|
||||
type: types.paragraph,
|
||||
children: children,
|
||||
styleId: properties.styleId || null,
|
||||
styleName: properties.styleName || null,
|
||||
numbering: properties.numbering || null,
|
||||
alignment: properties.alignment || null,
|
||||
indent: {
|
||||
start: indent.start || null,
|
||||
end: indent.end || null,
|
||||
firstLine: indent.firstLine || null,
|
||||
hanging: indent.hanging || null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function Run(children, properties) {
|
||||
properties = properties || {};
|
||||
return {
|
||||
type: types.run,
|
||||
children: children,
|
||||
styleId: properties.styleId || null,
|
||||
styleName: properties.styleName || null,
|
||||
isBold: !!properties.isBold,
|
||||
isUnderline: !!properties.isUnderline,
|
||||
isItalic: !!properties.isItalic,
|
||||
isStrikethrough: !!properties.isStrikethrough,
|
||||
isAllCaps: !!properties.isAllCaps,
|
||||
isSmallCaps: !!properties.isSmallCaps,
|
||||
verticalAlignment: properties.verticalAlignment || verticalAlignment.baseline,
|
||||
font: properties.font || null,
|
||||
fontSize: properties.fontSize || null,
|
||||
highlight: properties.highlight || null
|
||||
};
|
||||
}
|
||||
|
||||
var verticalAlignment = {
|
||||
baseline: "baseline",
|
||||
superscript: "superscript",
|
||||
subscript: "subscript"
|
||||
};
|
||||
|
||||
function Text(value) {
|
||||
return {
|
||||
type: types.text,
|
||||
value: value
|
||||
};
|
||||
}
|
||||
|
||||
function Tab() {
|
||||
return {
|
||||
type: types.tab
|
||||
};
|
||||
}
|
||||
|
||||
function Checkbox(options) {
|
||||
return {
|
||||
type: types.checkbox,
|
||||
checked: options.checked
|
||||
};
|
||||
}
|
||||
|
||||
function Hyperlink(children, options) {
|
||||
return {
|
||||
type: types.hyperlink,
|
||||
children: children,
|
||||
href: options.href,
|
||||
anchor: options.anchor,
|
||||
targetFrame: options.targetFrame
|
||||
};
|
||||
}
|
||||
|
||||
function NoteReference(options) {
|
||||
return {
|
||||
type: types.noteReference,
|
||||
noteType: options.noteType,
|
||||
noteId: options.noteId
|
||||
};
|
||||
}
|
||||
|
||||
function Notes(notes) {
|
||||
this._notes = _.indexBy(notes, function(note) {
|
||||
return noteKey(note.noteType, note.noteId);
|
||||
});
|
||||
}
|
||||
|
||||
Notes.prototype.resolve = function(reference) {
|
||||
return this.findNoteByKey(noteKey(reference.noteType, reference.noteId));
|
||||
};
|
||||
|
||||
Notes.prototype.findNoteByKey = function(key) {
|
||||
return this._notes[key] || null;
|
||||
};
|
||||
|
||||
function Note(options) {
|
||||
return {
|
||||
type: types.note,
|
||||
noteType: options.noteType,
|
||||
noteId: options.noteId,
|
||||
body: options.body
|
||||
};
|
||||
}
|
||||
|
||||
function commentReference(options) {
|
||||
return {
|
||||
type: types.commentReference,
|
||||
commentId: options.commentId
|
||||
};
|
||||
}
|
||||
|
||||
function comment(options) {
|
||||
return {
|
||||
type: types.comment,
|
||||
commentId: options.commentId,
|
||||
body: options.body,
|
||||
authorName: options.authorName,
|
||||
authorInitials: options.authorInitials
|
||||
};
|
||||
}
|
||||
|
||||
function noteKey(noteType, id) {
|
||||
return noteType + "-" + id;
|
||||
}
|
||||
|
||||
function Image(options) {
|
||||
return {
|
||||
type: types.image,
|
||||
// `read` is retained for backwards compatibility, but other read
|
||||
// methods should be preferred.
|
||||
read: function(encoding) {
|
||||
if (encoding) {
|
||||
return options.readImage(encoding);
|
||||
} else {
|
||||
return options.readImage().then(function(arrayBuffer) {
|
||||
return Buffer.from(arrayBuffer);
|
||||
});
|
||||
}
|
||||
},
|
||||
readAsArrayBuffer: function() {
|
||||
return options.readImage();
|
||||
},
|
||||
readAsBase64String: function() {
|
||||
return options.readImage("base64");
|
||||
},
|
||||
readAsBuffer: function() {
|
||||
return options.readImage().then(function(arrayBuffer) {
|
||||
return Buffer.from(arrayBuffer);
|
||||
});
|
||||
},
|
||||
altText: options.altText,
|
||||
contentType: options.contentType
|
||||
};
|
||||
}
|
||||
|
||||
function Table(children, properties) {
|
||||
properties = properties || {};
|
||||
return {
|
||||
type: types.table,
|
||||
children: children,
|
||||
styleId: properties.styleId || null,
|
||||
styleName: properties.styleName || null
|
||||
};
|
||||
}
|
||||
|
||||
function TableRow(children, options) {
|
||||
options = options || {};
|
||||
return {
|
||||
type: types.tableRow,
|
||||
children: children,
|
||||
isHeader: options.isHeader || false
|
||||
};
|
||||
}
|
||||
|
||||
function TableCell(children, options) {
|
||||
options = options || {};
|
||||
return {
|
||||
type: types.tableCell,
|
||||
children: children,
|
||||
colSpan: options.colSpan == null ? 1 : options.colSpan,
|
||||
rowSpan: options.rowSpan == null ? 1 : options.rowSpan
|
||||
};
|
||||
}
|
||||
|
||||
function Break(breakType) {
|
||||
return {
|
||||
type: types["break"],
|
||||
breakType: breakType
|
||||
};
|
||||
}
|
||||
|
||||
function BookmarkStart(options) {
|
||||
return {
|
||||
type: types.bookmarkStart,
|
||||
name: options.name
|
||||
};
|
||||
}
|
||||
|
||||
exports.document = exports.Document = Document;
|
||||
exports.paragraph = exports.Paragraph = Paragraph;
|
||||
exports.run = exports.Run = Run;
|
||||
exports.text = exports.Text = Text;
|
||||
exports.tab = exports.Tab = Tab;
|
||||
exports.checkbox = exports.Checkbox = Checkbox;
|
||||
exports.Hyperlink = Hyperlink;
|
||||
exports.noteReference = exports.NoteReference = NoteReference;
|
||||
exports.Notes = Notes;
|
||||
exports.Note = Note;
|
||||
exports.commentReference = commentReference;
|
||||
exports.comment = comment;
|
||||
exports.Image = Image;
|
||||
exports.Table = Table;
|
||||
exports.TableRow = TableRow;
|
||||
exports.TableCell = TableCell;
|
||||
exports.lineBreak = Break("line");
|
||||
exports.pageBreak = Break("page");
|
||||
exports.columnBreak = Break("column");
|
||||
exports.BookmarkStart = BookmarkStart;
|
||||
|
||||
exports.verticalAlignment = verticalAlignment;
|
||||
+817
@@ -0,0 +1,817 @@
|
||||
exports.createBodyReader = createBodyReader;
|
||||
exports._readNumberingProperties = readNumberingProperties;
|
||||
|
||||
var dingbatToUnicode = require("dingbat-to-unicode");
|
||||
var _ = require("underscore");
|
||||
|
||||
var documents = require("../documents");
|
||||
var Result = require("../results").Result;
|
||||
var warning = require("../results").warning;
|
||||
var xml = require("../xml");
|
||||
var transforms = require("../transforms");
|
||||
var uris = require("./uris");
|
||||
|
||||
function createBodyReader(options) {
|
||||
return {
|
||||
readXmlElement: function(element) {
|
||||
return new BodyReader(options).readXmlElement(element);
|
||||
},
|
||||
readXmlElements: function(elements) {
|
||||
return new BodyReader(options).readXmlElements(elements);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function BodyReader(options) {
|
||||
var complexFieldStack = [];
|
||||
var currentInstrText = [];
|
||||
|
||||
// When a paragraph is marked as deleted, its contents should be combined
|
||||
// with the following paragraph. See 17.13.5.15 del (Deleted Paragraph) of
|
||||
// ECMA-376 4th edition Part 1.
|
||||
var deletedParagraphContents = [];
|
||||
|
||||
var relationships = options.relationships;
|
||||
var contentTypes = options.contentTypes;
|
||||
var docxFile = options.docxFile;
|
||||
var files = options.files;
|
||||
var numbering = options.numbering;
|
||||
var styles = options.styles;
|
||||
|
||||
function readXmlElements(elements) {
|
||||
var results = elements.map(readXmlElement);
|
||||
return combineResults(results);
|
||||
}
|
||||
|
||||
function readXmlElement(element) {
|
||||
if (element.type === "element") {
|
||||
var handler = xmlElementReaders[element.name];
|
||||
if (handler) {
|
||||
return handler(element);
|
||||
} else if (!Object.prototype.hasOwnProperty.call(ignoreElements, element.name)) {
|
||||
var message = warning("An unrecognised element was ignored: " + element.name);
|
||||
return emptyResultWithMessages([message]);
|
||||
}
|
||||
}
|
||||
return emptyResult();
|
||||
}
|
||||
|
||||
function readParagraphProperties(element) {
|
||||
return readParagraphStyle(element).map(function(style) {
|
||||
return {
|
||||
type: "paragraphProperties",
|
||||
styleId: style.styleId,
|
||||
styleName: style.name,
|
||||
alignment: element.firstOrEmpty("w:jc").attributes["w:val"],
|
||||
numbering: readNumberingProperties(style.styleId, element.firstOrEmpty("w:numPr"), numbering),
|
||||
indent: readParagraphIndent(element.firstOrEmpty("w:ind"))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function readParagraphIndent(element) {
|
||||
return {
|
||||
start: element.attributes["w:start"] || element.attributes["w:left"],
|
||||
end: element.attributes["w:end"] || element.attributes["w:right"],
|
||||
firstLine: element.attributes["w:firstLine"],
|
||||
hanging: element.attributes["w:hanging"]
|
||||
};
|
||||
}
|
||||
|
||||
function readRunProperties(element) {
|
||||
return readRunStyle(element).map(function(style) {
|
||||
var fontSizeString = element.firstOrEmpty("w:sz").attributes["w:val"];
|
||||
// w:sz gives the font size in half points, so halve the value to get the size in points
|
||||
var fontSize = /^[0-9]+$/.test(fontSizeString) ? parseInt(fontSizeString, 10) / 2 : null;
|
||||
|
||||
return {
|
||||
type: "runProperties",
|
||||
styleId: style.styleId,
|
||||
styleName: style.name,
|
||||
verticalAlignment: element.firstOrEmpty("w:vertAlign").attributes["w:val"],
|
||||
font: element.firstOrEmpty("w:rFonts").attributes["w:ascii"],
|
||||
fontSize: fontSize,
|
||||
isBold: readBooleanElement(element.first("w:b")),
|
||||
isUnderline: readUnderline(element.first("w:u")),
|
||||
isItalic: readBooleanElement(element.first("w:i")),
|
||||
isStrikethrough: readBooleanElement(element.first("w:strike")),
|
||||
isAllCaps: readBooleanElement(element.first("w:caps")),
|
||||
isSmallCaps: readBooleanElement(element.first("w:smallCaps")),
|
||||
highlight: readHighlightValue(element.firstOrEmpty("w:highlight").attributes["w:val"])
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function readUnderline(element) {
|
||||
if (element) {
|
||||
var value = element.attributes["w:val"];
|
||||
return value !== undefined && value !== "false" && value !== "0" && value !== "none";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function readBooleanElement(element) {
|
||||
if (element) {
|
||||
var value = element.attributes["w:val"];
|
||||
return value !== "false" && value !== "0";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function readBooleanAttributeValue(value) {
|
||||
return value !== "false" && value !== "0";
|
||||
}
|
||||
|
||||
function readHighlightValue(value) {
|
||||
if (!value || value === "none") {
|
||||
return null;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function readParagraphStyle(element) {
|
||||
return readStyle(element, "w:pStyle", "Paragraph", styles.findParagraphStyleById);
|
||||
}
|
||||
|
||||
function readRunStyle(element) {
|
||||
return readStyle(element, "w:rStyle", "Run", styles.findCharacterStyleById);
|
||||
}
|
||||
|
||||
function readTableStyle(element) {
|
||||
return readStyle(element, "w:tblStyle", "Table", styles.findTableStyleById);
|
||||
}
|
||||
|
||||
function readStyle(element, styleTagName, styleType, findStyleById) {
|
||||
var messages = [];
|
||||
var styleElement = element.first(styleTagName);
|
||||
var styleId = null;
|
||||
var name = null;
|
||||
if (styleElement) {
|
||||
styleId = styleElement.attributes["w:val"];
|
||||
if (styleId) {
|
||||
var style = findStyleById(styleId);
|
||||
if (style) {
|
||||
name = style.name;
|
||||
} else {
|
||||
messages.push(undefinedStyleWarning(styleType, styleId));
|
||||
}
|
||||
}
|
||||
}
|
||||
return elementResultWithMessages({styleId: styleId, name: name}, messages);
|
||||
}
|
||||
|
||||
function readFldChar(element) {
|
||||
var type = element.attributes["w:fldCharType"];
|
||||
if (type === "begin") {
|
||||
complexFieldStack.push({type: "begin", fldChar: element});
|
||||
currentInstrText = [];
|
||||
} else if (type === "end") {
|
||||
if (complexFieldStack.length === 0) {
|
||||
return emptyResultWithMessages([warning(
|
||||
"Ignoring complex field end character without corresponding start character"
|
||||
)]);
|
||||
}
|
||||
|
||||
var complexFieldEnd = complexFieldStack.pop();
|
||||
if (complexFieldEnd.type === "begin") {
|
||||
complexFieldEnd = parseCurrentInstrText(complexFieldEnd);
|
||||
}
|
||||
if (complexFieldEnd.type === "checkbox") {
|
||||
return elementResult(documents.checkbox({
|
||||
checked: complexFieldEnd.checked
|
||||
}));
|
||||
}
|
||||
} else if (type === "separate") {
|
||||
if (complexFieldStack.length === 0) {
|
||||
return emptyResultWithMessages([warning(
|
||||
"Ignoring complex field separator character without corresponding start character"
|
||||
)]);
|
||||
}
|
||||
|
||||
var complexFieldSeparate = complexFieldStack.pop();
|
||||
var complexField = parseCurrentInstrText(complexFieldSeparate);
|
||||
complexFieldStack.push(complexField);
|
||||
}
|
||||
return emptyResult();
|
||||
}
|
||||
|
||||
function currentHyperlinkOptions() {
|
||||
var topHyperlink = _.last(complexFieldStack.filter(function(complexField) {
|
||||
return complexField.type === "hyperlink";
|
||||
}));
|
||||
return topHyperlink ? topHyperlink.options : null;
|
||||
}
|
||||
|
||||
function parseCurrentInstrText(complexField) {
|
||||
return parseInstrText(
|
||||
currentInstrText.join(''),
|
||||
complexField.type === "begin"
|
||||
? complexField.fldChar
|
||||
: xml.emptyElement
|
||||
);
|
||||
}
|
||||
|
||||
function parseInstrText(instrText, fldChar) {
|
||||
var linkResult = /^\s*HYPERLINK\s+(\\l\s+)?(?:"(.*)"|([^\\]\S*))/.exec(instrText);
|
||||
if (linkResult) {
|
||||
var location = linkResult[2] === undefined ? linkResult[3] : linkResult[2];
|
||||
var options = linkResult[1] === undefined ? {href: location} : {anchor: location};
|
||||
return {type: "hyperlink", options: options};
|
||||
}
|
||||
|
||||
var checkboxResult = /\s*FORMCHECKBOX\s*/.exec(instrText);
|
||||
if (checkboxResult) {
|
||||
var checkboxElement = fldChar
|
||||
.firstOrEmpty("w:ffData")
|
||||
.firstOrEmpty("w:checkBox");
|
||||
var checkedElement = checkboxElement.first("w:checked");
|
||||
var checked = checkedElement == null
|
||||
? readBooleanElement(checkboxElement.first("w:default"))
|
||||
: readBooleanElement(checkedElement);
|
||||
return {type: "checkbox", checked: checked};
|
||||
}
|
||||
|
||||
return {type: "unknown"};
|
||||
}
|
||||
|
||||
function readInstrText(element) {
|
||||
currentInstrText.push(element.text());
|
||||
return emptyResult();
|
||||
}
|
||||
|
||||
function readSymbol(element) {
|
||||
// See 17.3.3.30 sym (Symbol Character) of ECMA-376 4th edition Part 1
|
||||
var font = element.attributes["w:font"];
|
||||
var char = element.attributes["w:char"];
|
||||
var unicodeCharacter = dingbatToUnicode.hex(font, char);
|
||||
if (unicodeCharacter == null && /^F0..$/.test(char)) {
|
||||
unicodeCharacter = dingbatToUnicode.hex(font, char.substring(2));
|
||||
}
|
||||
|
||||
if (unicodeCharacter == null) {
|
||||
return emptyResultWithMessages([warning(
|
||||
"A w:sym element with an unsupported character was ignored: char " + char + " in font " + font
|
||||
)]);
|
||||
} else {
|
||||
return elementResult(new documents.Text(unicodeCharacter.string));
|
||||
}
|
||||
}
|
||||
|
||||
function noteReferenceReader(noteType) {
|
||||
return function(element) {
|
||||
var noteId = element.attributes["w:id"];
|
||||
return elementResult(new documents.NoteReference({
|
||||
noteType: noteType,
|
||||
noteId: noteId
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
function readCommentReference(element) {
|
||||
return elementResult(documents.commentReference({
|
||||
commentId: element.attributes["w:id"]
|
||||
}));
|
||||
}
|
||||
|
||||
function readChildElements(element) {
|
||||
return readXmlElements(element.children);
|
||||
}
|
||||
|
||||
var xmlElementReaders = {
|
||||
"w:p": function(element) {
|
||||
var paragraphPropertiesElement = element.firstOrEmpty("w:pPr");
|
||||
|
||||
var isDeleted = !!paragraphPropertiesElement
|
||||
.firstOrEmpty("w:rPr")
|
||||
.first("w:del");
|
||||
|
||||
if (isDeleted) {
|
||||
element.children.forEach(function(child) {
|
||||
deletedParagraphContents.push(child);
|
||||
});
|
||||
return emptyResult();
|
||||
} else {
|
||||
var childrenXml = element.children;
|
||||
if (deletedParagraphContents.length > 0) {
|
||||
childrenXml = deletedParagraphContents.concat(childrenXml);
|
||||
deletedParagraphContents = [];
|
||||
}
|
||||
return ReadResult.map(
|
||||
readParagraphProperties(paragraphPropertiesElement),
|
||||
readXmlElements(childrenXml),
|
||||
function(properties, children) {
|
||||
return new documents.Paragraph(children, properties);
|
||||
}
|
||||
).insertExtra();
|
||||
}
|
||||
},
|
||||
"w:r": function(element) {
|
||||
return ReadResult.map(
|
||||
readRunProperties(element.firstOrEmpty("w:rPr")),
|
||||
readXmlElements(element.children),
|
||||
function(properties, children) {
|
||||
var hyperlinkOptions = currentHyperlinkOptions();
|
||||
if (hyperlinkOptions !== null) {
|
||||
children = [new documents.Hyperlink(children, hyperlinkOptions)];
|
||||
}
|
||||
|
||||
return new documents.Run(children, properties);
|
||||
}
|
||||
);
|
||||
},
|
||||
"w:fldChar": readFldChar,
|
||||
"w:instrText": readInstrText,
|
||||
"w:t": function(element) {
|
||||
return elementResult(new documents.Text(element.text()));
|
||||
},
|
||||
"w:tab": function(element) {
|
||||
return elementResult(new documents.Tab());
|
||||
},
|
||||
"w:noBreakHyphen": function() {
|
||||
return elementResult(new documents.Text("\u2011"));
|
||||
},
|
||||
"w:softHyphen": function(element) {
|
||||
return elementResult(new documents.Text("\u00AD"));
|
||||
},
|
||||
"w:sym": readSymbol,
|
||||
"w:hyperlink": function(element) {
|
||||
var relationshipId = element.attributes["r:id"];
|
||||
var anchor = element.attributes["w:anchor"];
|
||||
return readXmlElements(element.children).map(function(children) {
|
||||
function create(options) {
|
||||
var targetFrame = element.attributes["w:tgtFrame"] || null;
|
||||
|
||||
return new documents.Hyperlink(
|
||||
children,
|
||||
_.extend({targetFrame: targetFrame}, options)
|
||||
);
|
||||
}
|
||||
|
||||
if (relationshipId) {
|
||||
var href = relationships.findTargetByRelationshipId(relationshipId);
|
||||
if (anchor) {
|
||||
href = uris.replaceFragment(href, anchor);
|
||||
}
|
||||
return create({href: href});
|
||||
} else if (anchor) {
|
||||
return create({anchor: anchor});
|
||||
} else {
|
||||
return children;
|
||||
}
|
||||
});
|
||||
},
|
||||
"w:tbl": readTable,
|
||||
"w:tr": readTableRow,
|
||||
"w:tc": readTableCell,
|
||||
"w:footnoteReference": noteReferenceReader("footnote"),
|
||||
"w:endnoteReference": noteReferenceReader("endnote"),
|
||||
"w:commentReference": readCommentReference,
|
||||
"w:br": function(element) {
|
||||
var breakType = element.attributes["w:type"];
|
||||
if (breakType == null || breakType === "textWrapping") {
|
||||
return elementResult(documents.lineBreak);
|
||||
} else if (breakType === "page") {
|
||||
return elementResult(documents.pageBreak);
|
||||
} else if (breakType === "column") {
|
||||
return elementResult(documents.columnBreak);
|
||||
} else {
|
||||
return emptyResultWithMessages([warning("Unsupported break type: " + breakType)]);
|
||||
}
|
||||
},
|
||||
"w:bookmarkStart": function(element){
|
||||
var name = element.attributes["w:name"];
|
||||
if (name === "_GoBack") {
|
||||
return emptyResult();
|
||||
} else {
|
||||
return elementResult(new documents.BookmarkStart({name: name}));
|
||||
}
|
||||
},
|
||||
|
||||
"mc:AlternateContent": function(element) {
|
||||
return readChildElements(element.firstOrEmpty("mc:Fallback"));
|
||||
},
|
||||
|
||||
"w:sdt": function(element) {
|
||||
var contentResult = readXmlElements(element.firstOrEmpty("w:sdtContent").children);
|
||||
return contentResult.map(function(content) {
|
||||
// From the WordML standard: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/3350cb64-931f-41f7-8824-f18b2568ce66
|
||||
//
|
||||
// > A CT_SdtCheckbox element that specifies that the parent
|
||||
// > structured document tag is a checkbox when displayed in the
|
||||
// > document. The parent structured document tag contents MUST
|
||||
// > contain a single character and optionally an additional
|
||||
// > character in a deleted run.
|
||||
|
||||
var checkbox = element
|
||||
.firstOrEmpty("w:sdtPr")
|
||||
.first("wordml:checkbox");
|
||||
|
||||
if (checkbox) {
|
||||
var checkedElement = checkbox.first("wordml:checked");
|
||||
var isChecked = !!checkedElement && readBooleanAttributeValue(
|
||||
checkedElement.attributes["wordml:val"]
|
||||
);
|
||||
var documentCheckbox = documents.checkbox({
|
||||
checked: isChecked
|
||||
});
|
||||
|
||||
var hasCheckbox = false;
|
||||
var replacedContent = content.map(transforms._elementsOfType(
|
||||
documents.types.text,
|
||||
function(text) {
|
||||
if (text.value.length > 0 && !hasCheckbox) {
|
||||
hasCheckbox = true;
|
||||
return documentCheckbox;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
if (hasCheckbox) {
|
||||
return replacedContent;
|
||||
} else {
|
||||
return documentCheckbox;
|
||||
}
|
||||
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
"w:customXml": readChildElements,
|
||||
"w:ins": readChildElements,
|
||||
"w:moveFromRangeEnd": readChildElements,
|
||||
"w:moveFromRangeStart": readChildElements,
|
||||
"w:moveTo": readChildElements,
|
||||
"w:moveToRangeEnd": readChildElements,
|
||||
"w:moveToRangeStart": readChildElements,
|
||||
"w:object": readChildElements,
|
||||
"w:smartTag": readChildElements,
|
||||
"w:drawing": readChildElements,
|
||||
"w:pict": function(element) {
|
||||
return readChildElements(element).toExtra();
|
||||
},
|
||||
"v:roundrect": readChildElements,
|
||||
"v:shape": readChildElements,
|
||||
"v:textbox": readChildElements,
|
||||
"w:txbxContent": readChildElements,
|
||||
"wp:inline": readDrawingElement,
|
||||
"wp:anchor": readDrawingElement,
|
||||
"v:imagedata": readImageData,
|
||||
"v:group": readChildElements,
|
||||
"v:rect": readChildElements
|
||||
};
|
||||
|
||||
return {
|
||||
readXmlElement: readXmlElement,
|
||||
readXmlElements: readXmlElements
|
||||
};
|
||||
|
||||
|
||||
function readTable(element) {
|
||||
var propertiesResult = readTableProperties(element.firstOrEmpty("w:tblPr"));
|
||||
return readXmlElements(element.children)
|
||||
.flatMap(calculateRowSpans)
|
||||
.flatMap(function(children) {
|
||||
return propertiesResult.map(function(properties) {
|
||||
return documents.Table(children, properties);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function readTableProperties(element) {
|
||||
return readTableStyle(element).map(function(style) {
|
||||
return {
|
||||
styleId: style.styleId,
|
||||
styleName: style.name
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function readTableRow(element) {
|
||||
var properties = element.firstOrEmpty("w:trPr");
|
||||
|
||||
// See 17.13.5.12 del (Deleted Table Row) of ECMA-376 4th edition Part 1
|
||||
var isDeleted = !!properties.first("w:del");
|
||||
if (isDeleted) {
|
||||
return emptyResult();
|
||||
}
|
||||
|
||||
var isHeader = !!properties.first("w:tblHeader");
|
||||
return readXmlElements(element.children).map(function(children) {
|
||||
return documents.TableRow(children, {isHeader: isHeader});
|
||||
});
|
||||
}
|
||||
|
||||
function readTableCell(element) {
|
||||
return readXmlElements(element.children).map(function(children) {
|
||||
var properties = element.firstOrEmpty("w:tcPr");
|
||||
|
||||
var gridSpan = properties.firstOrEmpty("w:gridSpan").attributes["w:val"];
|
||||
var colSpan = gridSpan ? parseInt(gridSpan, 10) : 1;
|
||||
|
||||
var cell = documents.TableCell(children, {colSpan: colSpan});
|
||||
cell._vMerge = readVMerge(properties);
|
||||
return cell;
|
||||
});
|
||||
}
|
||||
|
||||
function readVMerge(properties) {
|
||||
var element = properties.first("w:vMerge");
|
||||
if (element) {
|
||||
var val = element.attributes["w:val"];
|
||||
return val === "continue" || !val;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function calculateRowSpans(rows) {
|
||||
var unexpectedNonRows = _.any(rows, function(row) {
|
||||
return row.type !== documents.types.tableRow;
|
||||
});
|
||||
if (unexpectedNonRows) {
|
||||
removeVMergeProperties(rows);
|
||||
return elementResultWithMessages(rows, [warning(
|
||||
"unexpected non-row element in table, cell merging may be incorrect"
|
||||
)]);
|
||||
}
|
||||
var unexpectedNonCells = _.any(rows, function(row) {
|
||||
return _.any(row.children, function(cell) {
|
||||
return cell.type !== documents.types.tableCell;
|
||||
});
|
||||
});
|
||||
if (unexpectedNonCells) {
|
||||
removeVMergeProperties(rows);
|
||||
return elementResultWithMessages(rows, [warning(
|
||||
"unexpected non-cell element in table row, cell merging may be incorrect"
|
||||
)]);
|
||||
}
|
||||
|
||||
var columns = {};
|
||||
|
||||
rows.forEach(function(row) {
|
||||
var cellIndex = 0;
|
||||
row.children.forEach(function(cell) {
|
||||
if (cell._vMerge && columns[cellIndex]) {
|
||||
columns[cellIndex].rowSpan++;
|
||||
} else {
|
||||
columns[cellIndex] = cell;
|
||||
cell._vMerge = false;
|
||||
}
|
||||
cellIndex += cell.colSpan;
|
||||
});
|
||||
});
|
||||
|
||||
rows.forEach(function(row) {
|
||||
row.children = row.children.filter(function(cell) {
|
||||
return !cell._vMerge;
|
||||
});
|
||||
row.children.forEach(function(cell) {
|
||||
delete cell._vMerge;
|
||||
});
|
||||
});
|
||||
|
||||
return elementResult(rows);
|
||||
}
|
||||
|
||||
function removeVMergeProperties(rows) {
|
||||
rows.forEach(function(row) {
|
||||
var cells = transforms.getDescendantsOfType(row, documents.types.tableCell);
|
||||
cells.forEach(function(cell) {
|
||||
delete cell._vMerge;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function readDrawingElement(element) {
|
||||
var blips = element
|
||||
.getElementsByTagName("a:graphic")
|
||||
.getElementsByTagName("a:graphicData")
|
||||
.getElementsByTagName("pic:pic")
|
||||
.getElementsByTagName("pic:blipFill")
|
||||
.getElementsByTagName("a:blip");
|
||||
|
||||
return combineResults(blips.map(readBlip.bind(null, element)));
|
||||
}
|
||||
|
||||
function readBlip(element, blip) {
|
||||
var propertiesElement = element.firstOrEmpty("wp:docPr");
|
||||
var properties = propertiesElement.attributes;
|
||||
|
||||
var altText = isBlank(properties.descr) ? properties.title : properties.descr;
|
||||
|
||||
var blipImageFile = findBlipImageFile(blip);
|
||||
if (blipImageFile === null) {
|
||||
return emptyResultWithMessages([warning("Could not find image file for a:blip element")]);
|
||||
}
|
||||
|
||||
return readImage(blipImageFile, altText).map(function(imageElement) {
|
||||
var hlinkClickElement = propertiesElement.firstOrEmpty("a:hlinkClick");
|
||||
var relationshipId = hlinkClickElement.attributes["r:id"];
|
||||
if (relationshipId) {
|
||||
var href = relationships.findTargetByRelationshipId(relationshipId);
|
||||
return new documents.Hyperlink([imageElement], {href: href});
|
||||
} else {
|
||||
return imageElement;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isBlank(value) {
|
||||
return value == null || /^\s*$/.test(value);
|
||||
}
|
||||
|
||||
function findBlipImageFile(blip) {
|
||||
var embedRelationshipId = blip.attributes["r:embed"];
|
||||
var linkRelationshipId = blip.attributes["r:link"];
|
||||
if (embedRelationshipId) {
|
||||
return findEmbeddedImageFile(embedRelationshipId);
|
||||
} else if (linkRelationshipId) {
|
||||
var imagePath = relationships.findTargetByRelationshipId(linkRelationshipId);
|
||||
return {
|
||||
path: imagePath,
|
||||
read: files.read.bind(files, imagePath)
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function readImageData(element) {
|
||||
var relationshipId = element.attributes['r:id'];
|
||||
|
||||
if (relationshipId) {
|
||||
return readImage(
|
||||
findEmbeddedImageFile(relationshipId),
|
||||
element.attributes["o:title"]);
|
||||
} else {
|
||||
return emptyResultWithMessages([warning("A v:imagedata element without a relationship ID was ignored")]);
|
||||
}
|
||||
}
|
||||
|
||||
function findEmbeddedImageFile(relationshipId) {
|
||||
var path = uris.uriToZipEntryName("word", relationships.findTargetByRelationshipId(relationshipId));
|
||||
return {
|
||||
path: path,
|
||||
read: docxFile.read.bind(docxFile, path)
|
||||
};
|
||||
}
|
||||
|
||||
function readImage(imageFile, altText) {
|
||||
var contentType = contentTypes.findContentType(imageFile.path);
|
||||
|
||||
var image = documents.Image({
|
||||
readImage: imageFile.read,
|
||||
altText: altText,
|
||||
contentType: contentType
|
||||
});
|
||||
var warnings = supportedImageTypes[contentType] ?
|
||||
[] : warning("Image of type " + contentType + " is unlikely to display in web browsers");
|
||||
return elementResultWithMessages(image, warnings);
|
||||
}
|
||||
|
||||
function undefinedStyleWarning(type, styleId) {
|
||||
return warning(
|
||||
type + " style with ID " + styleId + " was referenced but not defined in the document");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function readNumberingProperties(styleId, element, numbering) {
|
||||
var level = element.firstOrEmpty("w:ilvl").attributes["w:val"];
|
||||
var numId = element.firstOrEmpty("w:numId").attributes["w:val"];
|
||||
if (level !== undefined && numId !== undefined) {
|
||||
return numbering.findLevel(numId, level);
|
||||
}
|
||||
|
||||
if (styleId != null) {
|
||||
var levelByStyleId = numbering.findLevelByParagraphStyleId(styleId);
|
||||
if (levelByStyleId != null) {
|
||||
return levelByStyleId;
|
||||
}
|
||||
}
|
||||
|
||||
// Some malformed documents define numbering levels without an index, and
|
||||
// reference the numbering using a w:numPr element without a w:ilvl child.
|
||||
// To handle such cases, we assume a level of 0 as a fallback.
|
||||
if (numId !== undefined) {
|
||||
return numbering.findLevel(numId, "0");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var supportedImageTypes = {
|
||||
"image/png": true,
|
||||
"image/gif": true,
|
||||
"image/jpeg": true,
|
||||
"image/svg+xml": true,
|
||||
"image/tiff": true
|
||||
};
|
||||
|
||||
var ignoreElements = {
|
||||
"office-word:wrap": true,
|
||||
"v:shadow": true,
|
||||
"v:shapetype": true,
|
||||
"w:annotationRef": true,
|
||||
"w:bookmarkEnd": true,
|
||||
"w:sectPr": true,
|
||||
"w:proofErr": true,
|
||||
"w:lastRenderedPageBreak": true,
|
||||
"w:commentRangeStart": true,
|
||||
"w:commentRangeEnd": true,
|
||||
"w:del": true,
|
||||
"w:footnoteRef": true,
|
||||
"w:endnoteRef": true,
|
||||
"w:moveFrom": true,
|
||||
"w:pPr": true,
|
||||
"w:rPr": true,
|
||||
"w:tblPr": true,
|
||||
"w:tblGrid": true,
|
||||
"w:trPr": true,
|
||||
"w:tcPr": true
|
||||
};
|
||||
|
||||
function emptyResultWithMessages(messages) {
|
||||
return new ReadResult(null, null, messages);
|
||||
}
|
||||
|
||||
function emptyResult() {
|
||||
return new ReadResult(null);
|
||||
}
|
||||
|
||||
function elementResult(element) {
|
||||
return new ReadResult(element);
|
||||
}
|
||||
|
||||
function elementResultWithMessages(element, messages) {
|
||||
return new ReadResult(element, null, messages);
|
||||
}
|
||||
|
||||
function ReadResult(element, extra, messages) {
|
||||
this.value = element || [];
|
||||
this.extra = extra || [];
|
||||
this._result = new Result({
|
||||
element: this.value,
|
||||
extra: extra
|
||||
}, messages);
|
||||
this.messages = this._result.messages;
|
||||
}
|
||||
|
||||
ReadResult.prototype.toExtra = function() {
|
||||
return new ReadResult(null, joinElements(this.extra, this.value), this.messages);
|
||||
};
|
||||
|
||||
ReadResult.prototype.insertExtra = function() {
|
||||
var extra = this.extra;
|
||||
if (extra && extra.length) {
|
||||
return new ReadResult(joinElements(this.value, extra), null, this.messages);
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
ReadResult.prototype.map = function(func) {
|
||||
var result = this._result.map(function(value) {
|
||||
return func(value.element);
|
||||
});
|
||||
return new ReadResult(result.value, this.extra, result.messages);
|
||||
};
|
||||
|
||||
ReadResult.prototype.flatMap = function(func) {
|
||||
var result = this._result.flatMap(function(value) {
|
||||
return func(value.element)._result;
|
||||
});
|
||||
return new ReadResult(result.value.element, joinElements(this.extra, result.value.extra), result.messages);
|
||||
};
|
||||
|
||||
ReadResult.map = function(first, second, func) {
|
||||
return new ReadResult(
|
||||
func(first.value, second.value),
|
||||
joinElements(first.extra, second.extra),
|
||||
first.messages.concat(second.messages)
|
||||
);
|
||||
};
|
||||
|
||||
function combineResults(results) {
|
||||
var result = Result.combine(_.pluck(results, "_result"));
|
||||
return new ReadResult(
|
||||
_.flatten(_.pluck(result.value, "element")),
|
||||
_.filter(_.flatten(_.pluck(result.value, "extra")), identity),
|
||||
result.messages
|
||||
);
|
||||
}
|
||||
|
||||
function joinElements(first, second) {
|
||||
return _.flatten([first, second]);
|
||||
}
|
||||
|
||||
function identity(value) {
|
||||
return value;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
var documents = require("../documents");
|
||||
var Result = require("../results").Result;
|
||||
|
||||
function createCommentsReader(bodyReader) {
|
||||
function readCommentsXml(element) {
|
||||
return Result.combine(element.getElementsByTagName("w:comment")
|
||||
.map(readCommentElement));
|
||||
}
|
||||
|
||||
function readCommentElement(element) {
|
||||
var id = element.attributes["w:id"];
|
||||
|
||||
function readOptionalAttribute(name) {
|
||||
return (element.attributes[name] || "").trim() || null;
|
||||
}
|
||||
|
||||
return bodyReader.readXmlElements(element.children)
|
||||
.map(function(body) {
|
||||
return documents.comment({
|
||||
commentId: id,
|
||||
body: body,
|
||||
authorName: readOptionalAttribute("w:author"),
|
||||
authorInitials: readOptionalAttribute("w:initials")
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return readCommentsXml;
|
||||
}
|
||||
|
||||
exports.createCommentsReader = createCommentsReader;
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
exports.readContentTypesFromXml = readContentTypesFromXml;
|
||||
|
||||
var fallbackContentTypes = {
|
||||
"png": "png",
|
||||
"gif": "gif",
|
||||
"jpeg": "jpeg",
|
||||
"jpg": "jpeg",
|
||||
"tif": "tiff",
|
||||
"tiff": "tiff",
|
||||
"bmp": "bmp"
|
||||
};
|
||||
|
||||
exports.defaultContentTypes = contentTypes({}, {});
|
||||
|
||||
|
||||
function readContentTypesFromXml(element) {
|
||||
var extensionDefaults = Object.create(null);
|
||||
var overrides = Object.create(null);
|
||||
|
||||
element.children.forEach(function(child) {
|
||||
if (child.name === "content-types:Default") {
|
||||
extensionDefaults[child.attributes.Extension] = child.attributes.ContentType;
|
||||
}
|
||||
if (child.name === "content-types:Override") {
|
||||
var name = child.attributes.PartName;
|
||||
if (name.charAt(0) === "/") {
|
||||
name = name.substring(1);
|
||||
}
|
||||
overrides[name] = child.attributes.ContentType;
|
||||
}
|
||||
});
|
||||
return contentTypes(overrides, extensionDefaults);
|
||||
}
|
||||
|
||||
function contentTypes(overrides, extensionDefaults) {
|
||||
return {
|
||||
findContentType: function(path) {
|
||||
var overrideContentType = overrides[path];
|
||||
if (overrideContentType) {
|
||||
return overrideContentType;
|
||||
} else {
|
||||
var pathParts = path.split(".");
|
||||
var extension = pathParts[pathParts.length - 1];
|
||||
if (Object.prototype.hasOwnProperty.call(extensionDefaults, extension)) {
|
||||
return extensionDefaults[extension];
|
||||
} else {
|
||||
var fallback = fallbackContentTypes[extension.toLowerCase()];
|
||||
if (fallback) {
|
||||
return "image/" + fallback;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
exports.DocumentXmlReader = DocumentXmlReader;
|
||||
|
||||
var documents = require("../documents");
|
||||
var Result = require("../results").Result;
|
||||
|
||||
|
||||
function DocumentXmlReader(options) {
|
||||
var bodyReader = options.bodyReader;
|
||||
|
||||
function convertXmlToDocument(element) {
|
||||
var body = element.first("w:body");
|
||||
|
||||
if (body == null) {
|
||||
throw new Error("Could not find the body element: are you sure this is a docx file?");
|
||||
}
|
||||
|
||||
var result = bodyReader.readXmlElements(body.children)
|
||||
.map(function(children) {
|
||||
return new documents.Document(children, {
|
||||
notes: options.notes,
|
||||
comments: options.comments
|
||||
});
|
||||
});
|
||||
return new Result(result.value, result.messages);
|
||||
}
|
||||
|
||||
return {
|
||||
convertXmlToDocument: convertXmlToDocument
|
||||
};
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
exports.read = read;
|
||||
exports._findPartPaths = findPartPaths;
|
||||
|
||||
var promises = require("../promises");
|
||||
var documents = require("../documents");
|
||||
var Result = require("../results").Result;
|
||||
var zipfile = require("../zipfile");
|
||||
|
||||
var readXmlFromZipFile = require("./office-xml-reader").readXmlFromZipFile;
|
||||
var createBodyReader = require("./body-reader").createBodyReader;
|
||||
var DocumentXmlReader = require("./document-xml-reader").DocumentXmlReader;
|
||||
var relationshipsReader = require("./relationships-reader");
|
||||
var contentTypesReader = require("./content-types-reader");
|
||||
var numberingXml = require("./numbering-xml");
|
||||
var stylesReader = require("./styles-reader");
|
||||
var notesReader = require("./notes-reader");
|
||||
var commentsReader = require("./comments-reader");
|
||||
var Files = require("./files").Files;
|
||||
|
||||
|
||||
function read(docxFile, input, options) {
|
||||
input = input || {};
|
||||
options = options || {};
|
||||
|
||||
var files = new Files({
|
||||
externalFileAccess: options.externalFileAccess,
|
||||
relativeToFile: input.path
|
||||
});
|
||||
|
||||
return promises.props({
|
||||
contentTypes: readContentTypesFromZipFile(docxFile),
|
||||
partPaths: findPartPaths(docxFile),
|
||||
docxFile: docxFile,
|
||||
files: files
|
||||
}).also(function(result) {
|
||||
return {
|
||||
styles: readStylesFromZipFile(docxFile, result.partPaths.styles)
|
||||
};
|
||||
}).also(function(result) {
|
||||
return {
|
||||
numbering: readNumberingFromZipFile(docxFile, result.partPaths.numbering, result.styles)
|
||||
};
|
||||
}).also(function(result) {
|
||||
return {
|
||||
footnotes: readXmlFileWithBody(result.partPaths.footnotes, result, function(bodyReader, xml) {
|
||||
if (xml) {
|
||||
return notesReader.createFootnotesReader(bodyReader)(xml);
|
||||
} else {
|
||||
return new Result([]);
|
||||
}
|
||||
}),
|
||||
endnotes: readXmlFileWithBody(result.partPaths.endnotes, result, function(bodyReader, xml) {
|
||||
if (xml) {
|
||||
return notesReader.createEndnotesReader(bodyReader)(xml);
|
||||
} else {
|
||||
return new Result([]);
|
||||
}
|
||||
}),
|
||||
comments: readXmlFileWithBody(result.partPaths.comments, result, function(bodyReader, xml) {
|
||||
if (xml) {
|
||||
return commentsReader.createCommentsReader(bodyReader)(xml);
|
||||
} else {
|
||||
return new Result([]);
|
||||
}
|
||||
})
|
||||
};
|
||||
}).also(function(result) {
|
||||
return {
|
||||
notes: result.footnotes.flatMap(function(footnotes) {
|
||||
return result.endnotes.map(function(endnotes) {
|
||||
return new documents.Notes(footnotes.concat(endnotes));
|
||||
});
|
||||
})
|
||||
};
|
||||
}).then(function(result) {
|
||||
return readXmlFileWithBody(result.partPaths.mainDocument, result, function(bodyReader, xml) {
|
||||
return result.notes.flatMap(function(notes) {
|
||||
return result.comments.flatMap(function(comments) {
|
||||
var reader = new DocumentXmlReader({
|
||||
bodyReader: bodyReader,
|
||||
notes: notes,
|
||||
comments: comments
|
||||
});
|
||||
return reader.convertXmlToDocument(xml);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function findPartPaths(docxFile) {
|
||||
return readPackageRelationships(docxFile).then(function(packageRelationships) {
|
||||
var mainDocumentPath = findPartPath({
|
||||
docxFile: docxFile,
|
||||
relationships: packageRelationships,
|
||||
relationshipType: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
||||
basePath: "",
|
||||
fallbackPath: "word/document.xml"
|
||||
});
|
||||
|
||||
if (!docxFile.exists(mainDocumentPath)) {
|
||||
throw new Error("Could not find main document part. Are you sure this is a valid .docx file?");
|
||||
}
|
||||
|
||||
return xmlFileReader({
|
||||
filename: relationshipsFilename(mainDocumentPath),
|
||||
readElement: relationshipsReader.readRelationships,
|
||||
defaultValue: relationshipsReader.defaultValue
|
||||
})(docxFile).then(function(documentRelationships) {
|
||||
function findPartRelatedToMainDocument(name) {
|
||||
return findPartPath({
|
||||
docxFile: docxFile,
|
||||
relationships: documentRelationships,
|
||||
relationshipType: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/" + name,
|
||||
basePath: zipfile.splitPath(mainDocumentPath).dirname,
|
||||
fallbackPath: "word/" + name + ".xml"
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
mainDocument: mainDocumentPath,
|
||||
comments: findPartRelatedToMainDocument("comments"),
|
||||
endnotes: findPartRelatedToMainDocument("endnotes"),
|
||||
footnotes: findPartRelatedToMainDocument("footnotes"),
|
||||
numbering: findPartRelatedToMainDocument("numbering"),
|
||||
styles: findPartRelatedToMainDocument("styles")
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function findPartPath(options) {
|
||||
var docxFile = options.docxFile;
|
||||
var relationships = options.relationships;
|
||||
var relationshipType = options.relationshipType;
|
||||
var basePath = options.basePath;
|
||||
var fallbackPath = options.fallbackPath;
|
||||
|
||||
var targets = relationships.findTargetsByType(relationshipType);
|
||||
var normalisedTargets = targets.map(function(target) {
|
||||
return stripPrefix(zipfile.joinPath(basePath, target), "/");
|
||||
});
|
||||
var validTargets = normalisedTargets.filter(function(target) {
|
||||
return docxFile.exists(target);
|
||||
});
|
||||
if (validTargets.length === 0) {
|
||||
return fallbackPath;
|
||||
} else {
|
||||
return validTargets[0];
|
||||
}
|
||||
}
|
||||
|
||||
function stripPrefix(value, prefix) {
|
||||
if (value.substring(0, prefix.length) === prefix) {
|
||||
return value.substring(prefix.length);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function xmlFileReader(options) {
|
||||
return function(zipFile) {
|
||||
return readXmlFromZipFile(zipFile, options.filename)
|
||||
.then(function(element) {
|
||||
return element ? options.readElement(element) : options.defaultValue;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function readXmlFileWithBody(filename, options, func) {
|
||||
var readRelationshipsFromZipFile = xmlFileReader({
|
||||
filename: relationshipsFilename(filename),
|
||||
readElement: relationshipsReader.readRelationships,
|
||||
defaultValue: relationshipsReader.defaultValue
|
||||
});
|
||||
|
||||
return readRelationshipsFromZipFile(options.docxFile).then(function(relationships) {
|
||||
var bodyReader = new createBodyReader({
|
||||
relationships: relationships,
|
||||
contentTypes: options.contentTypes,
|
||||
docxFile: options.docxFile,
|
||||
numbering: options.numbering,
|
||||
styles: options.styles,
|
||||
files: options.files
|
||||
});
|
||||
return readXmlFromZipFile(options.docxFile, filename)
|
||||
.then(function(xml) {
|
||||
return func(bodyReader, xml);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function relationshipsFilename(filename) {
|
||||
var split = zipfile.splitPath(filename);
|
||||
return zipfile.joinPath(split.dirname, "_rels", split.basename + ".rels");
|
||||
}
|
||||
|
||||
var readContentTypesFromZipFile = xmlFileReader({
|
||||
filename: "[Content_Types].xml",
|
||||
readElement: contentTypesReader.readContentTypesFromXml,
|
||||
defaultValue: contentTypesReader.defaultContentTypes
|
||||
});
|
||||
|
||||
function readNumberingFromZipFile(zipFile, path, styles) {
|
||||
return xmlFileReader({
|
||||
filename: path,
|
||||
readElement: function(element) {
|
||||
return numberingXml.readNumberingXml(element, {styles: styles});
|
||||
},
|
||||
defaultValue: numberingXml.defaultNumbering
|
||||
})(zipFile);
|
||||
}
|
||||
|
||||
function readStylesFromZipFile(zipFile, path) {
|
||||
return xmlFileReader({
|
||||
filename: path,
|
||||
readElement: stylesReader.readStylesXml,
|
||||
defaultValue: stylesReader.defaultStyles
|
||||
})(zipFile);
|
||||
}
|
||||
|
||||
var readPackageRelationships = xmlFileReader({
|
||||
filename: "_rels/.rels",
|
||||
readElement: relationshipsReader.readRelationships,
|
||||
defaultValue: relationshipsReader.defaultValue
|
||||
});
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
var url = require("url");
|
||||
var os = require("os");
|
||||
var dirname = require("path").dirname;
|
||||
var isAbsolutePath = require("path").isAbsolute;
|
||||
var resolvePath = require("path").resolve;
|
||||
|
||||
var fs = require("../fs");
|
||||
var promises = require("../promises");
|
||||
|
||||
|
||||
exports.Files = Files;
|
||||
exports.uriToPath = uriToPath;
|
||||
|
||||
|
||||
function Files(options) {
|
||||
options = options || {};
|
||||
if (!options.externalFileAccess) {
|
||||
return {
|
||||
read: function(uri) {
|
||||
return promises.reject(new Error("could not read external image '" + uri + "', external file access is disabled"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var base = options.relativeToFile ? dirname(options.relativeToFile) : null;
|
||||
|
||||
function read(uri, encoding) {
|
||||
return resolveUri(uri).then(function(path) {
|
||||
return fs.readFile(path, encoding).catch(function(error) {
|
||||
var message = "could not open external image: '" + uri + "' (document directory: '" + base + "')\n" + error.message;
|
||||
return promises.reject(new Error(message));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resolveUri(uri) {
|
||||
var path = uriToPath(uri);
|
||||
if (isAbsolutePath(path)) {
|
||||
return promises.resolve(path);
|
||||
} else if (base) {
|
||||
return promises.resolve(resolvePath(base, path));
|
||||
} else {
|
||||
return promises.reject(new Error("could not find external image '" + uri + "', path of input document is unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
read: read
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function uriToPath(uriString, platform) {
|
||||
if (!platform) {
|
||||
platform = os.platform();
|
||||
}
|
||||
|
||||
var uri = url.parse(uriString);
|
||||
if (isLocalFileUri(uri) || isRelativeUri(uri)) {
|
||||
var path = decodeURIComponent(uri.path);
|
||||
if (platform === "win32" && /^\/[a-z]:/i.test(path)) {
|
||||
return path.slice(1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
} else {
|
||||
throw new Error("Could not convert URI to path: " + uriString);
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalFileUri(uri) {
|
||||
return uri.protocol === "file:" && (!uri.host || uri.host === "localhost");
|
||||
}
|
||||
|
||||
function isRelativeUri(uri) {
|
||||
return !uri.protocol && !uri.host;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
var documents = require("../documents");
|
||||
var Result = require("../results").Result;
|
||||
|
||||
exports.createFootnotesReader = createReader.bind(this, "footnote");
|
||||
exports.createEndnotesReader = createReader.bind(this, "endnote");
|
||||
|
||||
function createReader(noteType, bodyReader) {
|
||||
function readNotesXml(element) {
|
||||
return Result.combine(element.getElementsByTagName("w:" + noteType)
|
||||
.filter(isFootnoteElement)
|
||||
.map(readFootnoteElement));
|
||||
}
|
||||
|
||||
function isFootnoteElement(element) {
|
||||
var type = element.attributes["w:type"];
|
||||
return type !== "continuationSeparator" && type !== "separator";
|
||||
}
|
||||
|
||||
function readFootnoteElement(footnoteElement) {
|
||||
var id = footnoteElement.attributes["w:id"];
|
||||
return bodyReader.readXmlElements(footnoteElement.children)
|
||||
.map(function(body) {
|
||||
return documents.Note({noteType: noteType, noteId: id, body: body});
|
||||
});
|
||||
}
|
||||
|
||||
return readNotesXml;
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
exports.readNumberingXml = readNumberingXml;
|
||||
exports.Numbering = Numbering;
|
||||
exports.defaultNumbering = new Numbering({}, {});
|
||||
|
||||
function Numbering(nums, abstractNums, styles) {
|
||||
var allLevels = _.flatten(_.values(abstractNums).map(function(abstractNum) {
|
||||
return _.values(abstractNum.levels);
|
||||
}));
|
||||
|
||||
var levelsByParagraphStyleId = _.indexBy(
|
||||
allLevels.filter(function(level) {
|
||||
return level.paragraphStyleId != null;
|
||||
}),
|
||||
"paragraphStyleId"
|
||||
);
|
||||
|
||||
function findLevel(numId, level) {
|
||||
return findLevelWithSeenNumIds(numId, level, Object.create(null));
|
||||
}
|
||||
|
||||
function findLevelWithSeenNumIds(numId, level, seenNumIds) {
|
||||
if (seenNumIds[numId]) {
|
||||
return null;
|
||||
}
|
||||
seenNumIds[numId] = true;
|
||||
|
||||
var num = nums[numId];
|
||||
if (!num) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var abstractNum = abstractNums[num.abstractNumId];
|
||||
if (!abstractNum) {
|
||||
return null;
|
||||
} else if (abstractNum.numStyleLink == null) {
|
||||
return abstractNums[num.abstractNumId].levels[level];
|
||||
} else {
|
||||
var style = styles.findNumberingStyleById(abstractNum.numStyleLink);
|
||||
return findLevelWithSeenNumIds(style.numId, level, seenNumIds);
|
||||
}
|
||||
}
|
||||
|
||||
function findLevelByParagraphStyleId(styleId) {
|
||||
return levelsByParagraphStyleId[styleId] || null;
|
||||
}
|
||||
|
||||
return {
|
||||
findLevel: findLevel,
|
||||
findLevelByParagraphStyleId: findLevelByParagraphStyleId
|
||||
};
|
||||
}
|
||||
|
||||
function readNumberingXml(root, options) {
|
||||
if (!options || !options.styles) {
|
||||
throw new Error("styles is missing");
|
||||
}
|
||||
|
||||
var abstractNums = readAbstractNums(root);
|
||||
var nums = readNums(root, abstractNums);
|
||||
return new Numbering(nums, abstractNums, options.styles);
|
||||
}
|
||||
|
||||
function readAbstractNums(root) {
|
||||
var abstractNums = Object.create(null);
|
||||
root.getElementsByTagName("w:abstractNum").forEach(function(element) {
|
||||
var id = element.attributes["w:abstractNumId"];
|
||||
abstractNums[id] = readAbstractNum(element);
|
||||
});
|
||||
return abstractNums;
|
||||
}
|
||||
|
||||
function readAbstractNum(element) {
|
||||
var levels = Object.create(null);
|
||||
|
||||
// Some malformed documents define numbering levels without an index, and
|
||||
// reference the numbering using a w:numPr element without a w:ilvl child.
|
||||
// To handle such cases, we assume a level of 0 as a fallback.
|
||||
var levelWithoutIndex = null;
|
||||
|
||||
element.getElementsByTagName("w:lvl").forEach(function(levelElement) {
|
||||
var levelIndex = levelElement.attributes["w:ilvl"];
|
||||
var numFmt = levelElement.firstOrEmpty("w:numFmt").attributes["w:val"];
|
||||
var isOrdered = numFmt !== "bullet";
|
||||
var paragraphStyleId = levelElement.firstOrEmpty("w:pStyle").attributes["w:val"];
|
||||
|
||||
if (levelIndex === undefined) {
|
||||
levelWithoutIndex = {
|
||||
isOrdered: isOrdered,
|
||||
level: "0",
|
||||
paragraphStyleId: paragraphStyleId
|
||||
};
|
||||
} else {
|
||||
levels[levelIndex] = {
|
||||
isOrdered: isOrdered,
|
||||
level: levelIndex,
|
||||
paragraphStyleId: paragraphStyleId
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (levelWithoutIndex !== null && levels[levelWithoutIndex.level] === undefined) {
|
||||
levels[levelWithoutIndex.level] = levelWithoutIndex;
|
||||
}
|
||||
|
||||
var numStyleLink = element.firstOrEmpty("w:numStyleLink").attributes["w:val"];
|
||||
|
||||
return {levels: levels, numStyleLink: numStyleLink};
|
||||
}
|
||||
|
||||
function readNums(root) {
|
||||
var nums = Object.create(null);
|
||||
root.getElementsByTagName("w:num").forEach(function(element) {
|
||||
var numId = element.attributes["w:numId"];
|
||||
var abstractNumId = element.first("w:abstractNumId").attributes["w:val"];
|
||||
nums[numId] = {abstractNumId: abstractNumId};
|
||||
});
|
||||
return nums;
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
var promises = require("../promises");
|
||||
var xml = require("../xml");
|
||||
|
||||
|
||||
exports.read = read;
|
||||
exports.readXmlFromZipFile = readXmlFromZipFile;
|
||||
|
||||
var xmlNamespaceMap = {
|
||||
// Transitional format
|
||||
"http://schemas.openxmlformats.org/wordprocessingml/2006/main": "w",
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships": "r",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing": "wp",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/main": "a",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/picture": "pic",
|
||||
|
||||
// Strict format
|
||||
"http://purl.oclc.org/ooxml/wordprocessingml/main": "w",
|
||||
"http://purl.oclc.org/ooxml/officeDocument/relationships": "r",
|
||||
"http://purl.oclc.org/ooxml/drawingml/wordprocessingDrawing": "wp",
|
||||
"http://purl.oclc.org/ooxml/drawingml/main": "a",
|
||||
"http://purl.oclc.org/ooxml/drawingml/picture": "pic",
|
||||
|
||||
// Common
|
||||
"http://schemas.openxmlformats.org/package/2006/content-types": "content-types",
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships": "relationships",
|
||||
"http://schemas.openxmlformats.org/markup-compatibility/2006": "mc",
|
||||
"urn:schemas-microsoft-com:vml": "v",
|
||||
"urn:schemas-microsoft-com:office:word": "office-word",
|
||||
|
||||
// [MS-DOCX]: Word Extensions to the Office Open XML (.docx) File Format
|
||||
// https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/b839fe1f-e1ca-4fa6-8c26-5954d0abbccd
|
||||
"http://schemas.microsoft.com/office/word/2010/wordml": "wordml"
|
||||
};
|
||||
|
||||
|
||||
function read(xmlString) {
|
||||
return xml.readString(xmlString, xmlNamespaceMap)
|
||||
.then(function(document) {
|
||||
return collapseAlternateContent(document)[0];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function readXmlFromZipFile(docxFile, path) {
|
||||
if (docxFile.exists(path)) {
|
||||
return docxFile.read(path, "utf-8")
|
||||
.then(stripUtf8Bom)
|
||||
.then(read);
|
||||
} else {
|
||||
return promises.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function stripUtf8Bom(xmlString) {
|
||||
return xmlString.replace(/^\uFEFF/g, '');
|
||||
}
|
||||
|
||||
|
||||
function collapseAlternateContent(node) {
|
||||
if (node.type === "element") {
|
||||
if (node.name === "mc:AlternateContent") {
|
||||
return node.firstOrEmpty("mc:Fallback").children;
|
||||
} else {
|
||||
node.children = _.flatten(node.children.map(collapseAlternateContent, true));
|
||||
return [node];
|
||||
}
|
||||
} else {
|
||||
return [node];
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
exports.readRelationships = readRelationships;
|
||||
exports.defaultValue = new Relationships([]);
|
||||
exports.Relationships = Relationships;
|
||||
|
||||
|
||||
function readRelationships(element) {
|
||||
var relationships = [];
|
||||
element.children.forEach(function(child) {
|
||||
if (child.name === "relationships:Relationship") {
|
||||
var relationship = {
|
||||
relationshipId: child.attributes.Id,
|
||||
target: child.attributes.Target,
|
||||
type: child.attributes.Type
|
||||
};
|
||||
relationships.push(relationship);
|
||||
}
|
||||
});
|
||||
return new Relationships(relationships);
|
||||
}
|
||||
|
||||
function Relationships(relationships) {
|
||||
var targetsByRelationshipId = Object.create(null);
|
||||
relationships.forEach(function(relationship) {
|
||||
targetsByRelationshipId[relationship.relationshipId] = relationship.target;
|
||||
});
|
||||
|
||||
var targetsByType = Object.create(null);
|
||||
relationships.forEach(function(relationship) {
|
||||
if (!targetsByType[relationship.type]) {
|
||||
targetsByType[relationship.type] = [];
|
||||
}
|
||||
targetsByType[relationship.type].push(relationship.target);
|
||||
});
|
||||
|
||||
return {
|
||||
findTargetByRelationshipId: function(relationshipId) {
|
||||
return targetsByRelationshipId[relationshipId];
|
||||
},
|
||||
findTargetsByType: function(type) {
|
||||
return targetsByType[type] || [];
|
||||
}
|
||||
};
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
var promises = require("../promises");
|
||||
var xml = require("../xml");
|
||||
|
||||
exports.writeStyleMap = writeStyleMap;
|
||||
exports.readStyleMap = readStyleMap;
|
||||
|
||||
|
||||
var schema = "http://schemas.zwobble.org/mammoth/style-map";
|
||||
var styleMapPath = "mammoth/style-map";
|
||||
var styleMapAbsolutePath = "/" + styleMapPath;
|
||||
|
||||
function writeStyleMap(docxFile, styleMap) {
|
||||
docxFile.write(styleMapPath, styleMap);
|
||||
return updateRelationships(docxFile).then(function() {
|
||||
return updateContentTypes(docxFile);
|
||||
});
|
||||
}
|
||||
|
||||
function updateRelationships(docxFile) {
|
||||
var path = "word/_rels/document.xml.rels";
|
||||
var relationshipsUri = "http://schemas.openxmlformats.org/package/2006/relationships";
|
||||
var relationshipElementName = "{" + relationshipsUri + "}Relationship";
|
||||
return docxFile.read(path, "utf8")
|
||||
.then(xml.readString)
|
||||
.then(function(relationshipsContainer) {
|
||||
var relationships = relationshipsContainer.children;
|
||||
addOrUpdateElement(relationships, relationshipElementName, "Id", {
|
||||
"Id": "rMammothStyleMap",
|
||||
"Type": schema,
|
||||
"Target": styleMapAbsolutePath
|
||||
});
|
||||
|
||||
var namespaces = {"": relationshipsUri};
|
||||
return docxFile.write(path, xml.writeString(relationshipsContainer, namespaces));
|
||||
});
|
||||
}
|
||||
|
||||
function updateContentTypes(docxFile) {
|
||||
var path = "[Content_Types].xml";
|
||||
var contentTypesUri = "http://schemas.openxmlformats.org/package/2006/content-types";
|
||||
var overrideName = "{" + contentTypesUri + "}Override";
|
||||
return docxFile.read(path, "utf8")
|
||||
.then(xml.readString)
|
||||
.then(function(typesElement) {
|
||||
var children = typesElement.children;
|
||||
addOrUpdateElement(children, overrideName, "PartName", {
|
||||
"PartName": styleMapAbsolutePath,
|
||||
"ContentType": "text/prs.mammoth.style-map"
|
||||
});
|
||||
var namespaces = {"": contentTypesUri};
|
||||
return docxFile.write(path, xml.writeString(typesElement, namespaces));
|
||||
});
|
||||
}
|
||||
|
||||
function addOrUpdateElement(elements, name, identifyingAttribute, attributes) {
|
||||
var existingElement = _.find(elements, function(element) {
|
||||
return element.name === name &&
|
||||
element.attributes[identifyingAttribute] === attributes[identifyingAttribute];
|
||||
});
|
||||
if (existingElement) {
|
||||
existingElement.attributes = attributes;
|
||||
} else {
|
||||
elements.push(xml.element(name, attributes));
|
||||
}
|
||||
}
|
||||
|
||||
function readStyleMap(docxFile) {
|
||||
if (docxFile.exists(styleMapPath)) {
|
||||
return docxFile.read(styleMapPath, "utf8");
|
||||
} else {
|
||||
return promises.resolve(null);
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
exports.readStylesXml = readStylesXml;
|
||||
exports.Styles = Styles;
|
||||
exports.defaultStyles = new Styles({}, {});
|
||||
|
||||
function Styles(paragraphStyles, characterStyles, tableStyles, numberingStyles) {
|
||||
return {
|
||||
findParagraphStyleById: function(styleId) {
|
||||
return paragraphStyles[styleId];
|
||||
},
|
||||
findCharacterStyleById: function(styleId) {
|
||||
return characterStyles[styleId];
|
||||
},
|
||||
findTableStyleById: function(styleId) {
|
||||
return tableStyles[styleId];
|
||||
},
|
||||
findNumberingStyleById: function(styleId) {
|
||||
return numberingStyles[styleId];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Styles.EMPTY = new Styles({}, {}, {}, {});
|
||||
|
||||
function readStylesXml(root) {
|
||||
var paragraphStyles = Object.create(null);
|
||||
var characterStyles = Object.create(null);
|
||||
var tableStyles = Object.create(null);
|
||||
var numberingStyles = Object.create(null);
|
||||
|
||||
root.getElementsByTagName("w:style").forEach(function(styleElement) {
|
||||
var style = readStyleElement(styleElement);
|
||||
var styleSet;
|
||||
|
||||
switch (style.type) {
|
||||
case "paragraph":
|
||||
styleSet = paragraphStyles;
|
||||
break;
|
||||
|
||||
case "character":
|
||||
styleSet = characterStyles;
|
||||
break;
|
||||
|
||||
case "table":
|
||||
styleSet = tableStyles;
|
||||
break;
|
||||
|
||||
case "numbering":
|
||||
styleSet = numberingStyles;
|
||||
break;
|
||||
}
|
||||
|
||||
// Per 17.7.4.17 style (Style Definition) of ECMA-376 4th edition Part 1:
|
||||
//
|
||||
// > If multiple style definitions each declare the same value for their
|
||||
// > styleId, then the first such instance shall keep its current
|
||||
// > identifier with all other instances being reassigned in any manner
|
||||
// > desired.
|
||||
//
|
||||
// For the purpose of conversion, there's no point holding onto styles
|
||||
// with reassigned style IDs, so we ignore such style definitions.
|
||||
|
||||
if (styleSet && styleSet[style.styleId] === undefined) {
|
||||
styleSet[style.styleId] = style;
|
||||
}
|
||||
});
|
||||
|
||||
return new Styles(paragraphStyles, characterStyles, tableStyles, numberingStyles);
|
||||
}
|
||||
|
||||
function readStyleElement(styleElement) {
|
||||
var type = styleElement.attributes["w:type"];
|
||||
|
||||
if (type === "numbering") {
|
||||
return readNumberingStyleElement(type, styleElement);
|
||||
} else {
|
||||
var styleId = readStyleId(styleElement);
|
||||
var name = styleName(styleElement);
|
||||
return {type: type, styleId: styleId, name: name};
|
||||
}
|
||||
}
|
||||
|
||||
function styleName(styleElement) {
|
||||
var nameElement = styleElement.first("w:name");
|
||||
return nameElement ? nameElement.attributes["w:val"] : null;
|
||||
}
|
||||
|
||||
function readNumberingStyleElement(type, styleElement) {
|
||||
var styleId = readStyleId(styleElement);
|
||||
|
||||
var numId = styleElement
|
||||
.firstOrEmpty("w:pPr")
|
||||
.firstOrEmpty("w:numPr")
|
||||
.firstOrEmpty("w:numId")
|
||||
.attributes["w:val"];
|
||||
|
||||
return {type: type, numId: numId, styleId: styleId};
|
||||
}
|
||||
|
||||
function readStyleId(styleElement) {
|
||||
return styleElement.attributes["w:styleId"];
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
exports.uriToZipEntryName = uriToZipEntryName;
|
||||
exports.replaceFragment = replaceFragment;
|
||||
|
||||
function uriToZipEntryName(base, uri) {
|
||||
if (uri.charAt(0) === "/") {
|
||||
return uri.substr(1);
|
||||
} else {
|
||||
// In general, we should check first and second for trailing and leading slashes,
|
||||
// but in our specific case this seems to be sufficient
|
||||
return base + "/" + uri;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function replaceFragment(uri, fragment) {
|
||||
var hashIndex = uri.indexOf("#");
|
||||
if (hashIndex !== -1) {
|
||||
uri = uri.substring(0, hashIndex);
|
||||
}
|
||||
return uri + "#" + fragment;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
var fs = require("fs");
|
||||
|
||||
var promises = require("./promises");
|
||||
|
||||
function readFile(path, options) {
|
||||
return promises.resolve(fs.promises.readFile(path, options));
|
||||
}
|
||||
|
||||
function writeFile(path, data, options) {
|
||||
return promises.resolve(fs.promises.writeFile(path, data, options));
|
||||
}
|
||||
|
||||
exports.createWriteStream = fs.createWriteStream.bind(fs);
|
||||
exports.readFile = readFile;
|
||||
exports.readFileSync = fs.readFileSync.bind(fs);
|
||||
exports.writeFile = writeFile;
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
var htmlPaths = require("../styles/html-paths");
|
||||
|
||||
|
||||
function nonFreshElement(tagName, attributes, children) {
|
||||
return elementWithTag(
|
||||
htmlPaths.element(tagName, attributes, {fresh: false}),
|
||||
children);
|
||||
}
|
||||
|
||||
function freshElement(tagName, attributes, children) {
|
||||
var tag = htmlPaths.element(tagName, attributes, {fresh: true});
|
||||
return elementWithTag(tag, children);
|
||||
}
|
||||
|
||||
function elementWithTag(tag, children) {
|
||||
return {
|
||||
type: "element",
|
||||
tag: tag,
|
||||
children: children || []
|
||||
};
|
||||
}
|
||||
|
||||
function text(value) {
|
||||
return {
|
||||
type: "text",
|
||||
value: value
|
||||
};
|
||||
}
|
||||
|
||||
var forceWrite = {
|
||||
type: "forceWrite"
|
||||
};
|
||||
|
||||
exports.freshElement = freshElement;
|
||||
exports.nonFreshElement = nonFreshElement;
|
||||
exports.elementWithTag = elementWithTag;
|
||||
exports.text = text;
|
||||
exports.forceWrite = forceWrite;
|
||||
|
||||
var voidTagNames = {
|
||||
"br": true,
|
||||
"hr": true,
|
||||
"img": true,
|
||||
"input": true
|
||||
};
|
||||
|
||||
function isVoidElement(node) {
|
||||
return (node.children.length === 0) && voidTagNames[node.tag.tagName];
|
||||
}
|
||||
|
||||
exports.isVoidElement = isVoidElement;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
var ast = require("./ast");
|
||||
|
||||
exports.freshElement = ast.freshElement;
|
||||
exports.nonFreshElement = ast.nonFreshElement;
|
||||
exports.elementWithTag = ast.elementWithTag;
|
||||
exports.text = ast.text;
|
||||
exports.forceWrite = ast.forceWrite;
|
||||
|
||||
exports.simplify = require("./simplify");
|
||||
|
||||
function write(writer, nodes) {
|
||||
nodes.forEach(function(node) {
|
||||
writeNode(writer, node);
|
||||
});
|
||||
}
|
||||
|
||||
function writeNode(writer, node) {
|
||||
toStrings[node.type](writer, node);
|
||||
}
|
||||
|
||||
var toStrings = {
|
||||
element: generateElementString,
|
||||
text: generateTextString,
|
||||
forceWrite: function() { }
|
||||
};
|
||||
|
||||
function generateElementString(writer, node) {
|
||||
if (ast.isVoidElement(node)) {
|
||||
writer.selfClosing(node.tag.tagName, node.tag.attributes);
|
||||
} else {
|
||||
writer.open(node.tag.tagName, node.tag.attributes);
|
||||
write(writer, node.children);
|
||||
writer.close(node.tag.tagName);
|
||||
}
|
||||
}
|
||||
|
||||
function generateTextString(writer, node) {
|
||||
writer.text(node.value);
|
||||
}
|
||||
|
||||
exports.write = write;
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
var ast = require("./ast");
|
||||
|
||||
function simplify(nodes) {
|
||||
return collapse(removeEmpty(nodes));
|
||||
}
|
||||
|
||||
function collapse(nodes) {
|
||||
var children = [];
|
||||
|
||||
nodes.map(collapseNode).forEach(function(child) {
|
||||
appendChild(children, child);
|
||||
});
|
||||
return children;
|
||||
}
|
||||
|
||||
function collapseNode(node) {
|
||||
return collapsers[node.type](node);
|
||||
}
|
||||
|
||||
var collapsers = {
|
||||
element: collapseElement,
|
||||
text: identity,
|
||||
forceWrite: identity
|
||||
};
|
||||
|
||||
function collapseElement(node) {
|
||||
return ast.elementWithTag(node.tag, collapse(node.children));
|
||||
}
|
||||
|
||||
function identity(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function appendChild(children, child) {
|
||||
var lastChild = children[children.length - 1];
|
||||
if (child.type === "element" && !child.tag.fresh && lastChild && lastChild.type === "element" && child.tag.matchesElement(lastChild.tag)) {
|
||||
if (child.tag.separator) {
|
||||
appendChild(lastChild.children, ast.text(child.tag.separator));
|
||||
}
|
||||
child.children.forEach(function(grandChild) {
|
||||
// Mutation is fine since simplifying elements create a copy of the children.
|
||||
appendChild(lastChild.children, grandChild);
|
||||
});
|
||||
} else {
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
function removeEmpty(nodes) {
|
||||
return flatMap(nodes, function(node) {
|
||||
return emptiers[node.type](node);
|
||||
});
|
||||
}
|
||||
|
||||
function flatMap(values, func) {
|
||||
return _.flatten(_.map(values, func), true);
|
||||
}
|
||||
|
||||
var emptiers = {
|
||||
element: elementEmptier,
|
||||
text: textEmptier,
|
||||
forceWrite: neverEmpty
|
||||
};
|
||||
|
||||
function neverEmpty(node) {
|
||||
return [node];
|
||||
}
|
||||
|
||||
function elementEmptier(element) {
|
||||
var children = removeEmpty(element.children);
|
||||
if (children.length === 0 && !ast.isVoidElement(element)) {
|
||||
return [];
|
||||
} else {
|
||||
return [ast.elementWithTag(element.tag, children)];
|
||||
}
|
||||
}
|
||||
|
||||
function textEmptier(node) {
|
||||
if (node.value.length === 0) {
|
||||
return [];
|
||||
} else {
|
||||
return [node];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = simplify;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
var promises = require("./promises");
|
||||
var Html = require("./html");
|
||||
|
||||
exports.imgElement = imgElement;
|
||||
|
||||
function imgElement(func) {
|
||||
return function(element, messages) {
|
||||
return promises.resolve(func(element)).then(function(result) {
|
||||
var attributes = {};
|
||||
if (element.altText) {
|
||||
attributes.alt = element.altText;
|
||||
}
|
||||
_.extend(attributes, result);
|
||||
|
||||
return [Html.freshElement("img", attributes)];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Undocumented, but retained for backwards-compatibility with 0.3.x
|
||||
exports.inline = exports.imgElement;
|
||||
|
||||
exports.dataUri = imgElement(function(element) {
|
||||
return element.readAsBase64String().then(function(imageBuffer) {
|
||||
return {
|
||||
src: "data:" + element.contentType + ";base64," + imageBuffer
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
function imageFilenameExtension(image) {
|
||||
return image.contentType.split(/\/|\\/)[1];
|
||||
}
|
||||
|
||||
exports.imageFilenameExtension = imageFilenameExtension;
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
interface Mammoth {
|
||||
convertToHtml: (input: Input, options?: Options) => Promise<Result>;
|
||||
extractRawText: (input: Input) => Promise<Result>;
|
||||
embedStyleMap: (input: Input, styleMap: string) => Promise<{
|
||||
toArrayBuffer: () => ArrayBuffer,
|
||||
toBuffer: () => Buffer,
|
||||
}>;
|
||||
images: Images;
|
||||
}
|
||||
|
||||
type Input = NodeJsInput | BrowserInput;
|
||||
|
||||
type NodeJsInput = PathInput | BufferInput;
|
||||
|
||||
interface PathInput {
|
||||
path: string;
|
||||
}
|
||||
|
||||
interface BufferInput {
|
||||
buffer: Buffer;
|
||||
}
|
||||
|
||||
type BrowserInput = ArrayBufferInput;
|
||||
|
||||
interface ArrayBufferInput {
|
||||
arrayBuffer: ArrayBuffer;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
styleMap?: string | Array<string>;
|
||||
includeEmbeddedStyleMap?: boolean;
|
||||
includeDefaultStyleMap?: boolean;
|
||||
convertImage?: ImageConverter;
|
||||
ignoreEmptyParagraphs?: boolean;
|
||||
idPrefix?: string;
|
||||
externalFileAccess?: boolean;
|
||||
transformDocument?: (element: any) => any;
|
||||
}
|
||||
|
||||
interface ImageConverter {
|
||||
__mammothBrand: "ImageConverter";
|
||||
}
|
||||
|
||||
interface Image {
|
||||
contentType: string;
|
||||
readAsArrayBuffer: () => Promise<ArrayBuffer>;
|
||||
readAsBase64String: () => Promise<string>;
|
||||
readAsBuffer: () => Promise<Buffer>;
|
||||
read: ImageRead;
|
||||
}
|
||||
|
||||
interface ImageRead {
|
||||
(): Promise<Buffer>;
|
||||
(encoding: string): Promise<string>;
|
||||
}
|
||||
|
||||
interface ImageAttributes {
|
||||
src: string;
|
||||
}
|
||||
|
||||
interface Images {
|
||||
dataUri: ImageConverter;
|
||||
imgElement: (f: (image: Image) => Promise<ImageAttributes>) => ImageConverter;
|
||||
}
|
||||
|
||||
interface Result {
|
||||
value: string;
|
||||
messages: Array<Message>;
|
||||
}
|
||||
|
||||
type Message = Warning | Error;
|
||||
|
||||
interface Warning {
|
||||
type: "warning";
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface Error {
|
||||
type: "error";
|
||||
message: string;
|
||||
error: unknown;
|
||||
}
|
||||
|
||||
declare const mammoth: Mammoth;
|
||||
|
||||
export = mammoth;
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
var docxReader = require("./docx/docx-reader");
|
||||
var docxStyleMap = require("./docx/style-map");
|
||||
var DocumentConverter = require("./document-to-html").DocumentConverter;
|
||||
var convertElementToRawText = require("./raw-text").convertElementToRawText;
|
||||
var readStyle = require("./style-reader").readStyle;
|
||||
var readOptions = require("./options-reader").readOptions;
|
||||
var promises = require("./promises");
|
||||
var unzip = require("./unzip");
|
||||
var Result = require("./results").Result;
|
||||
|
||||
exports.convertToHtml = convertToHtml;
|
||||
exports.convertToMarkdown = convertToMarkdown;
|
||||
exports.convert = convert;
|
||||
exports.extractRawText = extractRawText;
|
||||
exports.images = require("./images");
|
||||
exports.transforms = require("./transforms");
|
||||
exports.underline = require("./underline");
|
||||
exports.embedStyleMap = embedStyleMap;
|
||||
exports.readEmbeddedStyleMap = readEmbeddedStyleMap;
|
||||
|
||||
function convertToHtml(input, options) {
|
||||
return convert(input, options);
|
||||
}
|
||||
|
||||
function convertToMarkdown(input, options) {
|
||||
var markdownOptions = Object.create(options || {});
|
||||
markdownOptions.outputFormat = "markdown";
|
||||
return convert(input, markdownOptions);
|
||||
}
|
||||
|
||||
function convert(input, options) {
|
||||
options = readOptions(options);
|
||||
|
||||
var result = unzip.openZip(input)
|
||||
.then(function(docxFile) {
|
||||
return docxStyleMap.readStyleMap(docxFile).then(function(styleMap) {
|
||||
options.embeddedStyleMap = styleMap;
|
||||
return docxFile;
|
||||
});
|
||||
})
|
||||
.then(function(docxFile) {
|
||||
return docxReader.read(docxFile, input, options)
|
||||
.then(function(documentResult) {
|
||||
return documentResult.map(options.transformDocument);
|
||||
})
|
||||
.then(function(documentResult) {
|
||||
return convertDocumentToHtml(documentResult, options);
|
||||
});
|
||||
});
|
||||
|
||||
return promises.toExternalPromise(result);
|
||||
}
|
||||
|
||||
function readEmbeddedStyleMap(input) {
|
||||
var result = unzip.openZip(input)
|
||||
.then(docxStyleMap.readStyleMap);
|
||||
|
||||
return promises.toExternalPromise(result);
|
||||
}
|
||||
|
||||
function convertDocumentToHtml(documentResult, options) {
|
||||
var styleMapResult = parseStyleMap(options.readStyleMap());
|
||||
var parsedOptions = _.extend({}, options, {
|
||||
styleMap: styleMapResult.value
|
||||
});
|
||||
var documentConverter = new DocumentConverter(parsedOptions);
|
||||
|
||||
return documentResult.flatMapThen(function(document) {
|
||||
return styleMapResult.flatMapThen(function(styleMap) {
|
||||
return documentConverter.convertToHtml(document);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function parseStyleMap(styleMap) {
|
||||
return Result.combine((styleMap || []).map(readStyle))
|
||||
.map(function(styleMap) {
|
||||
return styleMap.filter(function(styleMapping) {
|
||||
return !!styleMapping;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function extractRawText(input) {
|
||||
var result = unzip.openZip(input)
|
||||
.then(docxReader.read)
|
||||
.then(function(documentResult) {
|
||||
return documentResult.map(convertElementToRawText);
|
||||
});
|
||||
|
||||
return promises.toExternalPromise(result);
|
||||
}
|
||||
|
||||
function embedStyleMap(input, styleMap) {
|
||||
var result = unzip.openZip(input)
|
||||
.then(function(docxFile) {
|
||||
return docxStyleMap.writeStyleMap(docxFile, styleMap)
|
||||
.then(function() {
|
||||
return docxFile;
|
||||
});
|
||||
})
|
||||
.then(function(docxFile) {
|
||||
return docxFile.toArrayBuffer();
|
||||
})
|
||||
.then(function(arrayBuffer) {
|
||||
return {
|
||||
toArrayBuffer: function() {
|
||||
return arrayBuffer;
|
||||
},
|
||||
toBuffer: function() {
|
||||
return Buffer.from(arrayBuffer);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return promises.toExternalPromise(result);
|
||||
}
|
||||
|
||||
exports.styleMapping = function() {
|
||||
throw new Error('Use a raw string instead of mammoth.styleMapping e.g. "p[style-name=\'Title\'] => h1" instead of mammoth.styleMapping("p[style-name=\'Title\'] => h1")');
|
||||
};
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/* global process */
|
||||
|
||||
var path = require("path");
|
||||
|
||||
var mammoth = require("./");
|
||||
var fs = require("./fs");
|
||||
var images = require("./images");
|
||||
var promises = require("./promises");
|
||||
|
||||
function main(argv) {
|
||||
var docxPath = argv["docx-path"];
|
||||
var outputPath = argv["output-path"];
|
||||
var outputDir = argv.output_dir;
|
||||
var outputFormat = argv.output_format;
|
||||
var styleMapPath = argv.style_map;
|
||||
|
||||
var result = readStyleMap(styleMapPath).then(function(styleMap) {
|
||||
var options = {
|
||||
styleMap: styleMap,
|
||||
outputFormat: outputFormat
|
||||
};
|
||||
|
||||
if (outputDir) {
|
||||
var basename = path.basename(docxPath, ".docx");
|
||||
outputPath = path.join(outputDir, basename + ".html");
|
||||
var imageIndex = 0;
|
||||
options.convertImage = images.imgElement(function(element) {
|
||||
imageIndex++;
|
||||
var extension = images.imageFilenameExtension(element);
|
||||
var filename = imageIndex + (extension === undefined ? "" : "." + extension);
|
||||
|
||||
return element.read().then(function(imageBuffer) {
|
||||
var imagePath = path.join(outputDir, filename);
|
||||
return fs.writeFile(imagePath, imageBuffer);
|
||||
}).then(function() {
|
||||
return {src: filename};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return mammoth.convert({path: docxPath}, options)
|
||||
.then(function(result) {
|
||||
result.messages.forEach(function(message) {
|
||||
process.stderr.write(message.message);
|
||||
process.stderr.write("\n");
|
||||
});
|
||||
|
||||
var outputStream = outputPath ? fs.createWriteStream(outputPath) : process.stdout;
|
||||
|
||||
outputStream.write(result.value);
|
||||
});
|
||||
});
|
||||
|
||||
promises.toExternalPromise(result).done();
|
||||
}
|
||||
|
||||
function readStyleMap(styleMapPath) {
|
||||
if (styleMapPath) {
|
||||
return fs.readFile(styleMapPath, "utf8");
|
||||
} else {
|
||||
return promises.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = main;
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
exports.readOptions = readOptions;
|
||||
|
||||
|
||||
var _ = require("underscore");
|
||||
|
||||
var defaultStyleMap = exports._defaultStyleMap = [
|
||||
"p.Heading1 => h1:fresh",
|
||||
"p.Heading2 => h2:fresh",
|
||||
"p.Heading3 => h3:fresh",
|
||||
"p.Heading4 => h4:fresh",
|
||||
"p.Heading5 => h5:fresh",
|
||||
"p.Heading6 => h6:fresh",
|
||||
"p[style-name='Heading 1'] => h1:fresh",
|
||||
"p[style-name='Heading 2'] => h2:fresh",
|
||||
"p[style-name='Heading 3'] => h3:fresh",
|
||||
"p[style-name='Heading 4'] => h4:fresh",
|
||||
"p[style-name='Heading 5'] => h5:fresh",
|
||||
"p[style-name='Heading 6'] => h6:fresh",
|
||||
"p[style-name='heading 1'] => h1:fresh",
|
||||
"p[style-name='heading 2'] => h2:fresh",
|
||||
"p[style-name='heading 3'] => h3:fresh",
|
||||
"p[style-name='heading 4'] => h4:fresh",
|
||||
"p[style-name='heading 5'] => h5:fresh",
|
||||
"p[style-name='heading 6'] => h6:fresh",
|
||||
|
||||
// Apple Pages
|
||||
"p.Heading => h1:fresh",
|
||||
"p[style-name='Heading'] => h1:fresh",
|
||||
|
||||
"r[style-name='Strong'] => strong",
|
||||
|
||||
"p[style-name='footnote text'] => p:fresh",
|
||||
"r[style-name='footnote reference'] =>",
|
||||
"p[style-name='endnote text'] => p:fresh",
|
||||
"r[style-name='endnote reference'] =>",
|
||||
"p[style-name='annotation text'] => p:fresh",
|
||||
"r[style-name='annotation reference'] =>",
|
||||
|
||||
// LibreOffice
|
||||
"p[style-name='Footnote'] => p:fresh",
|
||||
"r[style-name='Footnote anchor'] =>",
|
||||
"p[style-name='Endnote'] => p:fresh",
|
||||
"r[style-name='Endnote anchor'] =>",
|
||||
|
||||
"p:unordered-list(1) => ul > li:fresh",
|
||||
"p:unordered-list(2) => ul|ol > li > ul > li:fresh",
|
||||
"p:unordered-list(3) => ul|ol > li > ul|ol > li > ul > li:fresh",
|
||||
"p:unordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh",
|
||||
"p:unordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh",
|
||||
"p:ordered-list(1) => ol > li:fresh",
|
||||
"p:ordered-list(2) => ul|ol > li > ol > li:fresh",
|
||||
"p:ordered-list(3) => ul|ol > li > ul|ol > li > ol > li:fresh",
|
||||
"p:ordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh",
|
||||
"p:ordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh",
|
||||
|
||||
"r[style-name='Hyperlink'] =>",
|
||||
|
||||
"p[style-name='Normal'] => p:fresh",
|
||||
|
||||
// Apple Pages
|
||||
"p.Body => p:fresh",
|
||||
"p[style-name='Body'] => p:fresh"
|
||||
];
|
||||
|
||||
var standardOptions = exports._standardOptions = {
|
||||
externalFileAccess: false,
|
||||
transformDocument: identity,
|
||||
includeDefaultStyleMap: true,
|
||||
includeEmbeddedStyleMap: true
|
||||
};
|
||||
|
||||
function readOptions(options) {
|
||||
options = options || {};
|
||||
return _.extend({}, standardOptions, options, {
|
||||
customStyleMap: readStyleMap(options.styleMap),
|
||||
readStyleMap: function() {
|
||||
var styleMap = this.customStyleMap;
|
||||
if (this.includeEmbeddedStyleMap) {
|
||||
styleMap = styleMap.concat(readStyleMap(this.embeddedStyleMap));
|
||||
}
|
||||
if (this.includeDefaultStyleMap) {
|
||||
styleMap = styleMap.concat(defaultStyleMap);
|
||||
}
|
||||
return styleMap;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function readStyleMap(styleMap) {
|
||||
if (!styleMap) {
|
||||
return [];
|
||||
} else if (_.isString(styleMap)) {
|
||||
return styleMap.split("\n")
|
||||
.map(function(line) {
|
||||
return line.trim();
|
||||
})
|
||||
.filter(function(line) {
|
||||
return line !== "" && line.charAt(0) !== "#";
|
||||
});
|
||||
} else {
|
||||
return styleMap;
|
||||
}
|
||||
}
|
||||
|
||||
function identity(value) {
|
||||
return value;
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
exports.defer = defer;
|
||||
exports.Promise = Promise;
|
||||
exports.resolve = Promise.resolve.bind(Promise);
|
||||
exports.reject = Promise.reject.bind(Promise);
|
||||
|
||||
exports.forEachSeries = function(array, func) {
|
||||
var arrayIndex = 0;
|
||||
|
||||
var deferred = defer();
|
||||
|
||||
var next = function() {
|
||||
if (arrayIndex < array.length) {
|
||||
var element = array[arrayIndex];
|
||||
arrayIndex++;
|
||||
func(element).then(next, deferred.reject);
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
next();
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
var props = exports.props = function(obj) {
|
||||
// We rely on .keys() and .values() returning properties in the same order.
|
||||
var keys = Object.keys(obj);
|
||||
var values = Object.values(obj);
|
||||
|
||||
var result = Promise.all(values).then(function(resolvedValues) {
|
||||
var resolvedObj = {};
|
||||
for (var index = 0; index < keys.length; index++) {
|
||||
resolvedObj[keys[index]] = resolvedValues[index];
|
||||
}
|
||||
return resolvedObj;
|
||||
});
|
||||
|
||||
return addAlsoToPromise(result);
|
||||
};
|
||||
|
||||
var alsoMethod = function(func) {
|
||||
var result = this.then(function(value) {
|
||||
var returnValue = _.extend({}, value, func(value));
|
||||
return props(returnValue);
|
||||
});
|
||||
|
||||
return addAlsoToPromise(result);
|
||||
};
|
||||
|
||||
function addAlsoToPromise(promise) {
|
||||
promise.also = alsoMethod;
|
||||
return promise;
|
||||
}
|
||||
|
||||
exports.try = function(func) {
|
||||
try {
|
||||
return Promise.resolve(func());
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
function defer() {
|
||||
var resolve;
|
||||
var reject;
|
||||
var promise = new Promise(function(resolveArg, rejectArg) {
|
||||
resolve = resolveArg;
|
||||
reject = rejectArg;
|
||||
});
|
||||
|
||||
return {
|
||||
resolve: resolve,
|
||||
reject: reject,
|
||||
promise: promise
|
||||
};
|
||||
}
|
||||
|
||||
function ExternalPromise(executor) {
|
||||
var promise = new Promise(executor);
|
||||
promise.__proto__ = ExternalPromise.prototype;
|
||||
return promise;
|
||||
}
|
||||
|
||||
ExternalPromise.__proto__ = Promise;
|
||||
ExternalPromise.prototype.__proto__ = Promise.prototype;
|
||||
|
||||
ExternalPromise.prototype.done = function(resolve, reject) {
|
||||
return this.then(resolve, reject).catch(function(error) {
|
||||
if (isNode()) {
|
||||
var stack = error instanceof Error ? error.stack : error;
|
||||
process.stderr.write("Fatal " + stack + "\n");
|
||||
process.exit(2);
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
throw error;
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Previous versions of Mammoth used bluebird instead of native promises. While
|
||||
* internal usage can be replaced easily enough, external users of the library
|
||||
* may have relied on values returned from function calls being a bluebird
|
||||
* promise.
|
||||
*
|
||||
* However, since the return value was only documented as a promise, not as a
|
||||
* bluebird promise, and it's relatively straightforward to wrap the return
|
||||
* value in a bluebird promise if necessary, changing the API to return a native
|
||||
* promise should be relatively safe.
|
||||
*
|
||||
* The only exception is that the docs previously used `.done()`: therefore,
|
||||
* when returning promises from the API to external users, a `done` method is
|
||||
* added.
|
||||
*/
|
||||
function toExternalPromise(promise) {
|
||||
return ExternalPromise.resolve(promise);
|
||||
}
|
||||
|
||||
exports.toExternalPromise = toExternalPromise;
|
||||
|
||||
/**
|
||||
* Detect whether we're running in node.js. This uses the same logic as in
|
||||
* bluebird for consistency with previous versions of Mammoth.
|
||||
*/
|
||||
function isNode() {
|
||||
if (typeof process === "undefined") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return {}.toString.call(process).toLowerCase() === "[object process]";
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
var documents = require("./documents");
|
||||
|
||||
function convertElementToRawText(element) {
|
||||
if (element.type === "text") {
|
||||
return element.value;
|
||||
} else if (element.type === documents.types.tab) {
|
||||
return "\t";
|
||||
} else {
|
||||
var tail = element.type === "paragraph" ? "\n\n" : "";
|
||||
return (element.children || []).map(convertElementToRawText).join("") + tail;
|
||||
}
|
||||
}
|
||||
|
||||
exports.convertElementToRawText = convertElementToRawText;
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
|
||||
exports.Result = Result;
|
||||
exports.success = success;
|
||||
exports.warning = warning;
|
||||
exports.error = error;
|
||||
|
||||
|
||||
function Result(value, messages) {
|
||||
this.value = value;
|
||||
this.messages = messages || [];
|
||||
}
|
||||
|
||||
Result.prototype.map = function(func) {
|
||||
return new Result(func(this.value), this.messages);
|
||||
};
|
||||
|
||||
Result.prototype.flatMap = function(func) {
|
||||
var funcResult = func(this.value);
|
||||
return new Result(funcResult.value, combineMessages([this, funcResult]));
|
||||
};
|
||||
|
||||
Result.prototype.flatMapThen = function(func) {
|
||||
var that = this;
|
||||
return func(this.value).then(function(otherResult) {
|
||||
return new Result(otherResult.value, combineMessages([that, otherResult]));
|
||||
});
|
||||
};
|
||||
|
||||
Result.combine = function(results) {
|
||||
var values = _.flatten(_.pluck(results, "value"));
|
||||
var messages = combineMessages(results);
|
||||
return new Result(values, messages);
|
||||
};
|
||||
|
||||
function success(value) {
|
||||
return new Result(value, []);
|
||||
}
|
||||
|
||||
function warning(message) {
|
||||
return {
|
||||
type: "warning",
|
||||
message: message
|
||||
};
|
||||
}
|
||||
|
||||
function error(exception) {
|
||||
return {
|
||||
type: "error",
|
||||
message: exception.message,
|
||||
error: exception
|
||||
};
|
||||
}
|
||||
|
||||
function combineMessages(results) {
|
||||
var messages = [];
|
||||
_.flatten(_.pluck(results, "messages"), true).forEach(function(message) {
|
||||
if (!containsMessage(messages, message)) {
|
||||
messages.push(message);
|
||||
}
|
||||
});
|
||||
return messages;
|
||||
}
|
||||
|
||||
function containsMessage(messages, message) {
|
||||
return _.find(messages, isSameMessage.bind(null, message)) !== undefined;
|
||||
}
|
||||
|
||||
function isSameMessage(first, second) {
|
||||
return first.type === second.type && first.message === second.message;
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
var _ = require("underscore");
|
||||
var lop = require("lop");
|
||||
|
||||
var documentMatchers = require("./styles/document-matchers");
|
||||
var htmlPaths = require("./styles/html-paths");
|
||||
var tokenise = require("./styles/parser/tokeniser").tokenise;
|
||||
var results = require("./results");
|
||||
|
||||
exports.readHtmlPath = readHtmlPath;
|
||||
exports.readDocumentMatcher = readDocumentMatcher;
|
||||
exports.readStyle = readStyle;
|
||||
|
||||
|
||||
function readStyle(string) {
|
||||
return parseString(styleRule, string);
|
||||
}
|
||||
|
||||
function createStyleRule() {
|
||||
return lop.rules.sequence(
|
||||
lop.rules.sequence.capture(documentMatcherRule()),
|
||||
lop.rules.tokenOfType("whitespace"),
|
||||
lop.rules.tokenOfType("arrow"),
|
||||
lop.rules.sequence.capture(lop.rules.optional(lop.rules.sequence(
|
||||
lop.rules.tokenOfType("whitespace"),
|
||||
lop.rules.sequence.capture(htmlPathRule())
|
||||
).head())),
|
||||
lop.rules.tokenOfType("end")
|
||||
).map(function(documentMatcher, htmlPath) {
|
||||
return {
|
||||
from: documentMatcher,
|
||||
to: htmlPath.valueOrElse(htmlPaths.empty)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function readDocumentMatcher(string) {
|
||||
return parseString(documentMatcherRule(), string);
|
||||
}
|
||||
|
||||
function documentMatcherRule() {
|
||||
var sequence = lop.rules.sequence;
|
||||
|
||||
var identifierToConstant = function(identifier, constant) {
|
||||
return lop.rules.then(
|
||||
lop.rules.token("identifier", identifier),
|
||||
function() {
|
||||
return constant;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var paragraphRule = identifierToConstant("p", documentMatchers.paragraph);
|
||||
var runRule = identifierToConstant("r", documentMatchers.run);
|
||||
|
||||
var elementTypeRule = lop.rules.firstOf("p or r or table",
|
||||
paragraphRule,
|
||||
runRule
|
||||
);
|
||||
|
||||
var styleIdRule = lop.rules.sequence(
|
||||
lop.rules.tokenOfType("dot"),
|
||||
lop.rules.sequence.cut(),
|
||||
lop.rules.sequence.capture(identifierRule)
|
||||
).map(function(styleId) {
|
||||
return {styleId: styleId};
|
||||
});
|
||||
|
||||
var styleNameMatcherRule = lop.rules.firstOf("style name matcher",
|
||||
lop.rules.then(
|
||||
lop.rules.sequence(
|
||||
lop.rules.tokenOfType("equals"),
|
||||
lop.rules.sequence.cut(),
|
||||
lop.rules.sequence.capture(stringRule)
|
||||
).head(),
|
||||
function(styleName) {
|
||||
return {styleName: documentMatchers.equalTo(styleName)};
|
||||
}
|
||||
),
|
||||
lop.rules.then(
|
||||
lop.rules.sequence(
|
||||
lop.rules.tokenOfType("startsWith"),
|
||||
lop.rules.sequence.cut(),
|
||||
lop.rules.sequence.capture(stringRule)
|
||||
).head(),
|
||||
function(styleName) {
|
||||
return {styleName: documentMatchers.startsWith(styleName)};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
var styleNameRule = lop.rules.sequence(
|
||||
lop.rules.tokenOfType("open-square-bracket"),
|
||||
lop.rules.sequence.cut(),
|
||||
lop.rules.token("identifier", "style-name"),
|
||||
lop.rules.sequence.capture(styleNameMatcherRule),
|
||||
lop.rules.tokenOfType("close-square-bracket")
|
||||
).head();
|
||||
|
||||
|
||||
var listTypeRule = lop.rules.firstOf("list type",
|
||||
identifierToConstant("ordered-list", {isOrdered: true}),
|
||||
identifierToConstant("unordered-list", {isOrdered: false})
|
||||
);
|
||||
var listRule = sequence(
|
||||
lop.rules.tokenOfType("colon"),
|
||||
sequence.capture(listTypeRule),
|
||||
sequence.cut(),
|
||||
lop.rules.tokenOfType("open-paren"),
|
||||
sequence.capture(integerRule),
|
||||
lop.rules.tokenOfType("close-paren")
|
||||
).map(function(listType, levelNumber) {
|
||||
return {
|
||||
list: {
|
||||
isOrdered: listType.isOrdered,
|
||||
levelIndex: levelNumber - 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function createMatcherSuffixesRule(rules) {
|
||||
var matcherSuffix = lop.rules.firstOf.apply(
|
||||
lop.rules.firstOf,
|
||||
["matcher suffix"].concat(rules)
|
||||
);
|
||||
var matcherSuffixes = lop.rules.zeroOrMore(matcherSuffix);
|
||||
return lop.rules.then(matcherSuffixes, function(suffixes) {
|
||||
var matcherOptions = {};
|
||||
suffixes.forEach(function(suffix) {
|
||||
_.extend(matcherOptions, suffix);
|
||||
});
|
||||
return matcherOptions;
|
||||
});
|
||||
}
|
||||
|
||||
var paragraphOrRun = sequence(
|
||||
sequence.capture(elementTypeRule),
|
||||
sequence.capture(createMatcherSuffixesRule([
|
||||
styleIdRule,
|
||||
styleNameRule,
|
||||
listRule
|
||||
]))
|
||||
).map(function(createMatcher, matcherOptions) {
|
||||
return createMatcher(matcherOptions);
|
||||
});
|
||||
|
||||
var table = sequence(
|
||||
lop.rules.token("identifier", "table"),
|
||||
sequence.capture(createMatcherSuffixesRule([
|
||||
styleIdRule,
|
||||
styleNameRule
|
||||
]))
|
||||
).map(function(options) {
|
||||
return documentMatchers.table(options);
|
||||
});
|
||||
|
||||
var bold = identifierToConstant("b", documentMatchers.bold);
|
||||
var italic = identifierToConstant("i", documentMatchers.italic);
|
||||
var underline = identifierToConstant("u", documentMatchers.underline);
|
||||
var strikethrough = identifierToConstant("strike", documentMatchers.strikethrough);
|
||||
var allCaps = identifierToConstant("all-caps", documentMatchers.allCaps);
|
||||
var smallCaps = identifierToConstant("small-caps", documentMatchers.smallCaps);
|
||||
|
||||
var highlight = sequence(
|
||||
lop.rules.token("identifier", "highlight"),
|
||||
lop.rules.sequence.capture(lop.rules.optional(lop.rules.sequence(
|
||||
lop.rules.tokenOfType("open-square-bracket"),
|
||||
lop.rules.sequence.cut(),
|
||||
lop.rules.token("identifier", "color"),
|
||||
lop.rules.tokenOfType("equals"),
|
||||
lop.rules.sequence.capture(stringRule),
|
||||
lop.rules.tokenOfType("close-square-bracket")
|
||||
).head()))
|
||||
).map(function(color) {
|
||||
return documentMatchers.highlight({
|
||||
color: color.valueOrElse(undefined)
|
||||
});
|
||||
});
|
||||
|
||||
var commentReference = identifierToConstant("comment-reference", documentMatchers.commentReference);
|
||||
|
||||
var breakMatcher = sequence(
|
||||
lop.rules.token("identifier", "br"),
|
||||
sequence.cut(),
|
||||
lop.rules.tokenOfType("open-square-bracket"),
|
||||
lop.rules.token("identifier", "type"),
|
||||
lop.rules.tokenOfType("equals"),
|
||||
sequence.capture(stringRule),
|
||||
lop.rules.tokenOfType("close-square-bracket")
|
||||
).map(function(breakType) {
|
||||
switch (breakType) {
|
||||
case "line":
|
||||
return documentMatchers.lineBreak;
|
||||
case "page":
|
||||
return documentMatchers.pageBreak;
|
||||
case "column":
|
||||
return documentMatchers.columnBreak;
|
||||
default:
|
||||
// TODO: handle unknown document matchers
|
||||
}
|
||||
});
|
||||
|
||||
return lop.rules.firstOf("element type",
|
||||
paragraphOrRun,
|
||||
table,
|
||||
bold,
|
||||
italic,
|
||||
underline,
|
||||
strikethrough,
|
||||
allCaps,
|
||||
smallCaps,
|
||||
highlight,
|
||||
commentReference,
|
||||
breakMatcher
|
||||
);
|
||||
}
|
||||
|
||||
function readHtmlPath(string) {
|
||||
return parseString(htmlPathRule(), string);
|
||||
}
|
||||
|
||||
function htmlPathRule() {
|
||||
var capture = lop.rules.sequence.capture;
|
||||
var whitespaceRule = lop.rules.tokenOfType("whitespace");
|
||||
var freshRule = lop.rules.then(
|
||||
lop.rules.optional(lop.rules.sequence(
|
||||
lop.rules.tokenOfType("colon"),
|
||||
lop.rules.token("identifier", "fresh")
|
||||
)),
|
||||
function(option) {
|
||||
return option.map(function() {
|
||||
return true;
|
||||
}).valueOrElse(false);
|
||||
}
|
||||
);
|
||||
|
||||
var separatorRule = lop.rules.then(
|
||||
lop.rules.optional(lop.rules.sequence(
|
||||
lop.rules.tokenOfType("colon"),
|
||||
lop.rules.token("identifier", "separator"),
|
||||
lop.rules.tokenOfType("open-paren"),
|
||||
capture(stringRule),
|
||||
lop.rules.tokenOfType("close-paren")
|
||||
).head()),
|
||||
function(option) {
|
||||
return option.valueOrElse("");
|
||||
}
|
||||
);
|
||||
|
||||
var tagNamesRule = lop.rules.oneOrMoreWithSeparator(
|
||||
identifierRule,
|
||||
lop.rules.tokenOfType("choice")
|
||||
);
|
||||
|
||||
var styleElementRule = lop.rules.sequence(
|
||||
capture(tagNamesRule),
|
||||
capture(lop.rules.zeroOrMore(attributeOrClassRule)),
|
||||
capture(freshRule),
|
||||
capture(separatorRule)
|
||||
).map(function(tagName, attributesList, fresh, separator) {
|
||||
var attributes = Object.create(null);
|
||||
var options = {};
|
||||
attributesList.forEach(function(attribute) {
|
||||
if (attribute.append && attributes[attribute.name]) {
|
||||
attributes[attribute.name] += " " + attribute.value;
|
||||
} else {
|
||||
attributes[attribute.name] = attribute.value;
|
||||
}
|
||||
});
|
||||
if (fresh) {
|
||||
options.fresh = true;
|
||||
}
|
||||
if (separator) {
|
||||
options.separator = separator;
|
||||
}
|
||||
return htmlPaths.element(tagName, attributes, options);
|
||||
});
|
||||
|
||||
return lop.rules.firstOf("html path",
|
||||
lop.rules.then(lop.rules.tokenOfType("bang"), function() {
|
||||
return htmlPaths.ignore;
|
||||
}),
|
||||
lop.rules.then(
|
||||
lop.rules.zeroOrMoreWithSeparator(
|
||||
styleElementRule,
|
||||
lop.rules.sequence(
|
||||
whitespaceRule,
|
||||
lop.rules.tokenOfType("gt"),
|
||||
whitespaceRule
|
||||
)
|
||||
),
|
||||
htmlPaths.elements
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var identifierRule = lop.rules.then(
|
||||
lop.rules.tokenOfType("identifier"),
|
||||
decodeEscapeSequences
|
||||
);
|
||||
var integerRule = lop.rules.tokenOfType("integer");
|
||||
|
||||
var stringRule = lop.rules.then(
|
||||
lop.rules.tokenOfType("string"),
|
||||
decodeEscapeSequences
|
||||
);
|
||||
|
||||
var escapeSequences = {
|
||||
"n": "\n",
|
||||
"r": "\r",
|
||||
"t": "\t"
|
||||
};
|
||||
|
||||
function decodeEscapeSequences(value) {
|
||||
return value.replace(/\\(.)/g, function(match, code) {
|
||||
return escapeSequences[code] || code;
|
||||
});
|
||||
}
|
||||
|
||||
var attributeRule = lop.rules.sequence(
|
||||
lop.rules.tokenOfType("open-square-bracket"),
|
||||
lop.rules.sequence.cut(),
|
||||
lop.rules.sequence.capture(identifierRule),
|
||||
lop.rules.tokenOfType("equals"),
|
||||
lop.rules.sequence.capture(stringRule),
|
||||
lop.rules.tokenOfType("close-square-bracket")
|
||||
).map(function(name, value) {
|
||||
return {name: name, value: value, append: false};
|
||||
});
|
||||
|
||||
var classRule = lop.rules.sequence(
|
||||
lop.rules.tokenOfType("dot"),
|
||||
lop.rules.sequence.cut(),
|
||||
lop.rules.sequence.capture(identifierRule)
|
||||
).map(function(className) {
|
||||
return {name: "class", value: className, append: true};
|
||||
});
|
||||
|
||||
var attributeOrClassRule = lop.rules.firstOf(
|
||||
"attribute or class",
|
||||
attributeRule,
|
||||
classRule
|
||||
);
|
||||
|
||||
function parseString(rule, string) {
|
||||
var tokens = tokenise(string);
|
||||
var parser = lop.Parser();
|
||||
var parseResult = parser.parseTokens(rule, tokens);
|
||||
if (parseResult.isSuccess()) {
|
||||
return results.success(parseResult.value());
|
||||
} else {
|
||||
return new results.Result(null, [results.warning(describeFailure(string, parseResult))]);
|
||||
}
|
||||
}
|
||||
|
||||
function describeFailure(input, parseResult) {
|
||||
return "Did not understand this style mapping, so ignored it: " + input + "\n" +
|
||||
parseResult.errors().map(describeError).join("\n");
|
||||
}
|
||||
|
||||
function describeError(error) {
|
||||
return "Error was at character number " + error.characterNumber() + ": " +
|
||||
"Expected " + error.expected + " but got " + error.actual;
|
||||
}
|
||||
|
||||
var styleRule = createStyleRule();
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
exports.paragraph = paragraph;
|
||||
exports.run = run;
|
||||
exports.table = table;
|
||||
exports.bold = new Matcher("bold");
|
||||
exports.italic = new Matcher("italic");
|
||||
exports.underline = new Matcher("underline");
|
||||
exports.strikethrough = new Matcher("strikethrough");
|
||||
exports.allCaps = new Matcher("allCaps");
|
||||
exports.smallCaps = new Matcher("smallCaps");
|
||||
exports.highlight = highlight;
|
||||
exports.commentReference = new Matcher("commentReference");
|
||||
exports.lineBreak = new BreakMatcher({breakType: "line"});
|
||||
exports.pageBreak = new BreakMatcher({breakType: "page"});
|
||||
exports.columnBreak = new BreakMatcher({breakType: "column"});
|
||||
exports.equalTo = equalTo;
|
||||
exports.startsWith = startsWith;
|
||||
|
||||
|
||||
function paragraph(options) {
|
||||
return new Matcher("paragraph", options);
|
||||
}
|
||||
|
||||
function run(options) {
|
||||
return new Matcher("run", options);
|
||||
}
|
||||
|
||||
function table(options) {
|
||||
return new Matcher("table", options);
|
||||
}
|
||||
|
||||
function highlight(options) {
|
||||
return new HighlightMatcher(options);
|
||||
}
|
||||
|
||||
function Matcher(elementType, options) {
|
||||
options = options || {};
|
||||
this._elementType = elementType;
|
||||
this._styleId = options.styleId;
|
||||
this._styleName = options.styleName;
|
||||
if (options.list) {
|
||||
this._listIndex = options.list.levelIndex;
|
||||
this._listIsOrdered = options.list.isOrdered;
|
||||
}
|
||||
}
|
||||
|
||||
Matcher.prototype.matches = function(element) {
|
||||
return element.type === this._elementType &&
|
||||
(this._styleId === undefined || element.styleId === this._styleId) &&
|
||||
(this._styleName === undefined || (element.styleName && this._styleName.operator(this._styleName.operand, element.styleName))) &&
|
||||
(this._listIndex === undefined || isList(element, this._listIndex, this._listIsOrdered)) &&
|
||||
(this._breakType === undefined || this._breakType === element.breakType);
|
||||
};
|
||||
|
||||
function HighlightMatcher(options) {
|
||||
options = options || {};
|
||||
this._color = options.color;
|
||||
}
|
||||
|
||||
HighlightMatcher.prototype.matches = function(element) {
|
||||
return element.type === "highlight" &&
|
||||
(this._color === undefined || element.color === this._color);
|
||||
};
|
||||
|
||||
function BreakMatcher(options) {
|
||||
options = options || {};
|
||||
this._breakType = options.breakType;
|
||||
}
|
||||
|
||||
BreakMatcher.prototype.matches = function(element) {
|
||||
return element.type === "break" &&
|
||||
(this._breakType === undefined || element.breakType === this._breakType);
|
||||
};
|
||||
|
||||
function isList(element, levelIndex, isOrdered) {
|
||||
return element.numbering &&
|
||||
element.numbering.level == levelIndex &&
|
||||
element.numbering.isOrdered == isOrdered;
|
||||
}
|
||||
|
||||
function equalTo(value) {
|
||||
return {
|
||||
operator: operatorEqualTo,
|
||||
operand: value
|
||||
};
|
||||
}
|
||||
|
||||
function startsWith(value) {
|
||||
return {
|
||||
operator: operatorStartsWith,
|
||||
operand: value
|
||||
};
|
||||
}
|
||||
|
||||
function operatorEqualTo(first, second) {
|
||||
return first.toUpperCase() === second.toUpperCase();
|
||||
}
|
||||
|
||||
function operatorStartsWith(first, second) {
|
||||
return second.toUpperCase().indexOf(first.toUpperCase()) === 0;
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
var html = require("../html");
|
||||
|
||||
exports.topLevelElement = topLevelElement;
|
||||
exports.elements = elements;
|
||||
exports.element = element;
|
||||
|
||||
function topLevelElement(tagName, attributes) {
|
||||
return elements([element(tagName, attributes, {fresh: true})]);
|
||||
}
|
||||
|
||||
function elements(elementStyles) {
|
||||
return new HtmlPath(elementStyles.map(function(elementStyle) {
|
||||
if (_.isString(elementStyle)) {
|
||||
return element(elementStyle);
|
||||
} else {
|
||||
return elementStyle;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function HtmlPath(elements) {
|
||||
this._elements = elements;
|
||||
}
|
||||
|
||||
HtmlPath.prototype.wrap = function wrap(children) {
|
||||
var result = children();
|
||||
for (var index = this._elements.length - 1; index >= 0; index--) {
|
||||
result = this._elements[index].wrapNodes(result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
function element(tagName, attributes, options) {
|
||||
options = options || {};
|
||||
return new Element(tagName, attributes, options);
|
||||
}
|
||||
|
||||
function Element(tagName, attributes, options) {
|
||||
var tagNames = Object.create(null);
|
||||
if (_.isArray(tagName)) {
|
||||
tagName.forEach(function(tagName) {
|
||||
tagNames[tagName] = true;
|
||||
});
|
||||
tagName = tagName[0];
|
||||
} else {
|
||||
tagNames[tagName] = true;
|
||||
}
|
||||
|
||||
this.tagName = tagName;
|
||||
this.tagNames = tagNames;
|
||||
this.attributes = attributes || {};
|
||||
this.fresh = options.fresh;
|
||||
this.separator = options.separator;
|
||||
}
|
||||
|
||||
Element.prototype.matchesElement = function(element) {
|
||||
return this.tagNames[element.tagName] && _.isEqual(this.attributes || {}, element.attributes || {});
|
||||
};
|
||||
|
||||
Element.prototype.wrap = function wrap(generateNodes) {
|
||||
return this.wrapNodes(generateNodes());
|
||||
};
|
||||
|
||||
Element.prototype.wrapNodes = function wrapNodes(nodes) {
|
||||
return [html.elementWithTag(this, nodes)];
|
||||
};
|
||||
|
||||
exports.empty = elements([]);
|
||||
exports.ignore = {
|
||||
wrap: function() {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
var lop = require("lop");
|
||||
var RegexTokeniser = lop.RegexTokeniser;
|
||||
|
||||
exports.tokenise = tokenise;
|
||||
|
||||
var stringPrefix = "'((?:\\\\(?:.|$)|[^'\\\\])*)";
|
||||
|
||||
function tokenise(string) {
|
||||
var identifierCharacter = "(?:[a-zA-Z\\-_]|\\\\.)";
|
||||
var tokeniser = new RegexTokeniser([
|
||||
{name: "identifier", regex: new RegExp("(" + identifierCharacter + "(?:" + identifierCharacter + "|[0-9])*)")},
|
||||
{name: "dot", regex: /\./},
|
||||
{name: "colon", regex: /:/},
|
||||
{name: "gt", regex: />/},
|
||||
{name: "whitespace", regex: /\s+/},
|
||||
{name: "arrow", regex: /=>/},
|
||||
{name: "equals", regex: /=/},
|
||||
{name: "startsWith", regex: /\^=/},
|
||||
{name: "open-paren", regex: /\(/},
|
||||
{name: "close-paren", regex: /\)/},
|
||||
{name: "open-square-bracket", regex: /\[/},
|
||||
{name: "close-square-bracket", regex: /\]/},
|
||||
{name: "string", regex: new RegExp(stringPrefix + "'")},
|
||||
{name: "unterminated-string", regex: new RegExp(stringPrefix)},
|
||||
{name: "integer", regex: /([0-9]+)/},
|
||||
{name: "choice", regex: /\|/},
|
||||
{name: "bang", regex: /(!)/}
|
||||
]);
|
||||
return tokeniser.tokenise(string);
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
exports.paragraph = paragraph;
|
||||
exports.run = run;
|
||||
exports._elements = elements;
|
||||
exports._elementsOfType = elementsOfType;
|
||||
exports.getDescendantsOfType = getDescendantsOfType;
|
||||
exports.getDescendants = getDescendants;
|
||||
|
||||
function paragraph(transform) {
|
||||
return elementsOfType("paragraph", transform);
|
||||
}
|
||||
|
||||
function run(transform) {
|
||||
return elementsOfType("run", transform);
|
||||
}
|
||||
|
||||
function elementsOfType(elementType, transform) {
|
||||
return elements(function(element) {
|
||||
if (element.type === elementType) {
|
||||
return transform(element);
|
||||
} else {
|
||||
return element;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function elements(transform) {
|
||||
return function transformElement(element) {
|
||||
if (element.children) {
|
||||
var children = _.map(element.children, transformElement);
|
||||
element = _.extend(element, {children: children});
|
||||
}
|
||||
return transform(element);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function getDescendantsOfType(element, type) {
|
||||
return getDescendants(element).filter(function(descendant) {
|
||||
return descendant.type === type;
|
||||
});
|
||||
}
|
||||
|
||||
function getDescendants(element) {
|
||||
var descendants = [];
|
||||
|
||||
visitDescendants(element, function(descendant) {
|
||||
descendants.push(descendant);
|
||||
});
|
||||
|
||||
return descendants;
|
||||
}
|
||||
|
||||
function visitDescendants(element, visit) {
|
||||
if (element.children) {
|
||||
element.children.forEach(function(child) {
|
||||
visitDescendants(child, visit);
|
||||
visit(child);
|
||||
});
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
var htmlPaths = require("./styles/html-paths");
|
||||
var Html = require("./html");
|
||||
|
||||
|
||||
exports.element = element;
|
||||
|
||||
function element(name) {
|
||||
return function(html) {
|
||||
return Html.elementWithTag(htmlPaths.element(name), [html]);
|
||||
};
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
var fs = require("./fs");
|
||||
var promises = require("./promises");
|
||||
var zipfile = require("./zipfile");
|
||||
|
||||
exports.openZip = openZip;
|
||||
|
||||
function openZip(options) {
|
||||
if (options.path) {
|
||||
return fs.readFile(options.path).then(zipfile.openArrayBuffer);
|
||||
} else if (options.buffer) {
|
||||
return promises.resolve(zipfile.openArrayBuffer(options.buffer));
|
||||
} else if (options.file) {
|
||||
return promises.resolve(options.file);
|
||||
} else {
|
||||
return promises.reject(new Error("Could not find file in options"));
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
exports.writer = writer;
|
||||
|
||||
function writer(options) {
|
||||
options = options || {};
|
||||
if (options.prettyPrint) {
|
||||
return prettyWriter();
|
||||
} else {
|
||||
return simpleWriter();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var indentedElements = {
|
||||
div: true,
|
||||
p: true,
|
||||
ul: true,
|
||||
li: true
|
||||
};
|
||||
|
||||
|
||||
function prettyWriter() {
|
||||
var indentationLevel = 0;
|
||||
var indentation = " ";
|
||||
var stack = [];
|
||||
var start = true;
|
||||
var inText = false;
|
||||
|
||||
var writer = simpleWriter();
|
||||
|
||||
function open(tagName, attributes) {
|
||||
if (indentedElements[tagName]) {
|
||||
indent();
|
||||
}
|
||||
stack.push(tagName);
|
||||
writer.open(tagName, attributes);
|
||||
if (indentedElements[tagName]) {
|
||||
indentationLevel++;
|
||||
}
|
||||
start = false;
|
||||
}
|
||||
|
||||
function close(tagName) {
|
||||
if (indentedElements[tagName]) {
|
||||
indentationLevel--;
|
||||
indent();
|
||||
}
|
||||
stack.pop();
|
||||
writer.close(tagName);
|
||||
}
|
||||
|
||||
function text(value) {
|
||||
startText();
|
||||
var text = isInPre() ? value : value.replace("\n", "\n" + indentation);
|
||||
writer.text(text);
|
||||
}
|
||||
|
||||
function selfClosing(tagName, attributes) {
|
||||
indent();
|
||||
writer.selfClosing(tagName, attributes);
|
||||
}
|
||||
|
||||
function insideIndentedElement() {
|
||||
return stack.length === 0 || indentedElements[stack[stack.length - 1]];
|
||||
}
|
||||
|
||||
function startText() {
|
||||
if (!inText) {
|
||||
indent();
|
||||
inText = true;
|
||||
}
|
||||
}
|
||||
|
||||
function indent() {
|
||||
inText = false;
|
||||
if (!start && insideIndentedElement() && !isInPre()) {
|
||||
writer._append("\n");
|
||||
for (var i = 0; i < indentationLevel; i++) {
|
||||
writer._append(indentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isInPre() {
|
||||
return _.some(stack, function(tagName) {
|
||||
return tagName === "pre";
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
asString: writer.asString,
|
||||
open: open,
|
||||
close: close,
|
||||
text: text,
|
||||
selfClosing: selfClosing
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function simpleWriter() {
|
||||
var fragments = [];
|
||||
|
||||
function open(tagName, attributes) {
|
||||
var attributeString = generateAttributeString(attributes);
|
||||
fragments.push("<" + tagName + attributeString + ">");
|
||||
}
|
||||
|
||||
function close(tagName) {
|
||||
fragments.push("</" + tagName + ">");
|
||||
}
|
||||
|
||||
function selfClosing(tagName, attributes) {
|
||||
var attributeString = generateAttributeString(attributes);
|
||||
fragments.push("<" + tagName + attributeString + " />");
|
||||
}
|
||||
|
||||
function generateAttributeString(attributes) {
|
||||
return _.map(attributes, function(value, key) {
|
||||
return " " + key + '="' + escapeHtmlAttribute(value) + '"';
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function text(value) {
|
||||
fragments.push(escapeHtmlText(value));
|
||||
}
|
||||
|
||||
function append(html) {
|
||||
fragments.push(html);
|
||||
}
|
||||
|
||||
function asString() {
|
||||
return fragments.join("");
|
||||
}
|
||||
|
||||
return {
|
||||
asString: asString,
|
||||
open: open,
|
||||
close: close,
|
||||
text: text,
|
||||
selfClosing: selfClosing,
|
||||
_append: append
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtmlText(value) {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function escapeHtmlAttribute(value) {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
var htmlWriter = require("./html-writer");
|
||||
var markdownWriter = require("./markdown-writer");
|
||||
|
||||
exports.writer = writer;
|
||||
|
||||
|
||||
function writer(options) {
|
||||
options = options || {};
|
||||
if (options.outputFormat === "markdown") {
|
||||
return markdownWriter.writer();
|
||||
} else {
|
||||
return htmlWriter.writer(options);
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
var htmlWriter = require("./html-writer");
|
||||
|
||||
function symmetricMarkdownElement(end) {
|
||||
return markdownElement(end, end);
|
||||
}
|
||||
|
||||
function markdownElement(start, end) {
|
||||
return function() {
|
||||
return {start: start, end: end};
|
||||
};
|
||||
}
|
||||
|
||||
function markdownLink(attributes) {
|
||||
var href = attributes.href || "";
|
||||
if (href) {
|
||||
return {
|
||||
start: "[",
|
||||
end: "](" + escapeMarkdown(href) + ")",
|
||||
anchorPosition: "before"
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function markdownImage(attributes) {
|
||||
var src = attributes.src || "";
|
||||
var altText = attributes.alt || "";
|
||||
if (src || altText) {
|
||||
return {start: " + ")"};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function markdownList(options) {
|
||||
return function(attributes, list) {
|
||||
return {
|
||||
start: list ? "\n" : "",
|
||||
end: list ? "" : "\n",
|
||||
list: {
|
||||
isOrdered: options.isOrdered,
|
||||
indent: list ? list.indent + 1 : 0,
|
||||
count: 0
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function markdownListItem(attributes, list, listItem) {
|
||||
list = list || {indent: 0, isOrdered: false, count: 0};
|
||||
list.count++;
|
||||
listItem.hasClosed = false;
|
||||
|
||||
var bullet = list.isOrdered ? list.count + "." : "-";
|
||||
var start = repeatString("\t", list.indent) + bullet + " ";
|
||||
|
||||
return {
|
||||
start: start,
|
||||
end: function() {
|
||||
if (!listItem.hasClosed) {
|
||||
listItem.hasClosed = true;
|
||||
return "\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var htmlToMarkdown = {
|
||||
"p": markdownElement("", "\n\n"),
|
||||
"br": markdownElement("", " \n"),
|
||||
"ul": markdownList({isOrdered: false}),
|
||||
"ol": markdownList({isOrdered: true}),
|
||||
"li": markdownListItem,
|
||||
"strong": symmetricMarkdownElement("__"),
|
||||
"em": symmetricMarkdownElement("*"),
|
||||
"a": markdownLink,
|
||||
"img": markdownImage
|
||||
};
|
||||
|
||||
(function() {
|
||||
for (var i = 1; i <= 6; i++) {
|
||||
htmlToMarkdown["h" + i] = markdownElement(repeatString("#", i) + " ", "\n\n");
|
||||
}
|
||||
})();
|
||||
|
||||
function repeatString(value, count) {
|
||||
return new Array(count + 1).join(value);
|
||||
}
|
||||
|
||||
function markdownWriter() {
|
||||
var fragments = [];
|
||||
var elementStack = [];
|
||||
var list = null;
|
||||
var listItem = {};
|
||||
|
||||
function open(tagName, attributes) {
|
||||
attributes = attributes || {};
|
||||
|
||||
var createElement = htmlToMarkdown[tagName] || function() {
|
||||
return {};
|
||||
};
|
||||
var element = createElement(attributes, list, listItem);
|
||||
elementStack.push({end: element.end, list: list});
|
||||
|
||||
if (element.list) {
|
||||
list = element.list;
|
||||
}
|
||||
|
||||
var anchorBeforeStart = element.anchorPosition === "before";
|
||||
if (anchorBeforeStart) {
|
||||
writeAnchor(attributes);
|
||||
}
|
||||
|
||||
fragments.push(element.start || "");
|
||||
if (!anchorBeforeStart) {
|
||||
writeAnchor(attributes);
|
||||
}
|
||||
}
|
||||
|
||||
function writeAnchor(attributes) {
|
||||
if (attributes.id) {
|
||||
var writer = htmlWriter.writer();
|
||||
writer.open("a", {id: attributes.id});
|
||||
writer.close("a");
|
||||
fragments.push(writer.asString());
|
||||
}
|
||||
}
|
||||
|
||||
function close(tagName) {
|
||||
var element = elementStack.pop();
|
||||
list = element.list;
|
||||
var end = _.isFunction(element.end) ? element.end() : element.end;
|
||||
fragments.push(end || "");
|
||||
}
|
||||
|
||||
function selfClosing(tagName, attributes) {
|
||||
open(tagName, attributes);
|
||||
close(tagName);
|
||||
}
|
||||
|
||||
function text(value) {
|
||||
fragments.push(escapeMarkdown(value));
|
||||
}
|
||||
|
||||
function asString() {
|
||||
return fragments.join("");
|
||||
}
|
||||
|
||||
return {
|
||||
asString: asString,
|
||||
open: open,
|
||||
close: close,
|
||||
text: text,
|
||||
selfClosing: selfClosing
|
||||
};
|
||||
}
|
||||
|
||||
exports.writer = markdownWriter;
|
||||
|
||||
function escapeMarkdown(value) {
|
||||
return value
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/([\`\*_\{\}\[\]\(\)\#\+\-\.\!])/g, '\\$1');
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
var nodes = require("./nodes");
|
||||
|
||||
exports.Element = nodes.Element;
|
||||
exports.element = nodes.element;
|
||||
exports.emptyElement = nodes.emptyElement;
|
||||
exports.text = nodes.text;
|
||||
exports.readString = require("./reader").readString;
|
||||
exports.writeString = require("./writer").writeString;
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
var _ = require("underscore");
|
||||
|
||||
|
||||
exports.Element = Element;
|
||||
exports.element = function(name, attributes, children) {
|
||||
return new Element(name, attributes, children);
|
||||
};
|
||||
exports.text = function(value) {
|
||||
return {
|
||||
type: "text",
|
||||
value: value
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var emptyElement = exports.emptyElement = {
|
||||
first: function() {
|
||||
return null;
|
||||
},
|
||||
firstOrEmpty: function() {
|
||||
return emptyElement;
|
||||
},
|
||||
attributes: {},
|
||||
children: []
|
||||
};
|
||||
|
||||
function Element(name, attributes, children) {
|
||||
this.type = "element";
|
||||
this.name = name;
|
||||
this.attributes = attributes || {};
|
||||
this.children = children || [];
|
||||
}
|
||||
|
||||
Element.prototype.first = function(name) {
|
||||
return _.find(this.children, function(child) {
|
||||
return child.name === name;
|
||||
});
|
||||
};
|
||||
|
||||
Element.prototype.firstOrEmpty = function(name) {
|
||||
return this.first(name) || emptyElement;
|
||||
};
|
||||
|
||||
Element.prototype.getElementsByTagName = function(name) {
|
||||
var elements = _.filter(this.children, function(child) {
|
||||
return child.name === name;
|
||||
});
|
||||
return toElementList(elements);
|
||||
};
|
||||
|
||||
Element.prototype.text = function() {
|
||||
if (this.children.length === 0) {
|
||||
return "";
|
||||
} else if (this.children.length !== 1 || this.children[0].type !== "text") {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
return this.children[0].value;
|
||||
};
|
||||
|
||||
var elementListPrototype = {
|
||||
getElementsByTagName: function(name) {
|
||||
return toElementList(_.flatten(this.map(function(element) {
|
||||
return element.getElementsByTagName(name);
|
||||
}, true)));
|
||||
}
|
||||
};
|
||||
|
||||
function toElementList(array) {
|
||||
return _.extend(array, elementListPrototype);
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
var promises = require("../promises");
|
||||
var _ = require("underscore");
|
||||
|
||||
var xmldom = require("./xmldom");
|
||||
var nodes = require("./nodes");
|
||||
var Element = nodes.Element;
|
||||
|
||||
exports.readString = readString;
|
||||
|
||||
var Node = xmldom.Node;
|
||||
|
||||
function readString(xmlString, namespaceMap) {
|
||||
namespaceMap = namespaceMap || {};
|
||||
|
||||
try {
|
||||
var document = xmldom.parseFromString(xmlString, "text/xml");
|
||||
} catch (error) {
|
||||
return promises.reject(error);
|
||||
}
|
||||
|
||||
if (document.documentElement.tagName === "parsererror") {
|
||||
return promises.resolve(new Error(document.documentElement.textContent));
|
||||
}
|
||||
|
||||
function convertNode(node) {
|
||||
switch (node.nodeType) {
|
||||
case Node.ELEMENT_NODE:
|
||||
return convertElement(node);
|
||||
case Node.TEXT_NODE:
|
||||
return nodes.text(node.nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
function convertElement(element) {
|
||||
var convertedName = convertName(element);
|
||||
|
||||
var convertedChildren = [];
|
||||
_.forEach(element.childNodes, function(childNode) {
|
||||
var convertedNode = convertNode(childNode);
|
||||
if (convertedNode) {
|
||||
convertedChildren.push(convertedNode);
|
||||
}
|
||||
});
|
||||
|
||||
var convertedAttributes = Object.create(null);
|
||||
_.forEach(element.attributes, function(attribute) {
|
||||
convertedAttributes[convertName(attribute)] = attribute.value;
|
||||
});
|
||||
|
||||
return new Element(convertedName, convertedAttributes, convertedChildren);
|
||||
}
|
||||
|
||||
function convertName(node) {
|
||||
if (node.namespaceURI) {
|
||||
var mappedPrefix = namespaceMap[node.namespaceURI];
|
||||
var prefix;
|
||||
if (mappedPrefix) {
|
||||
prefix = mappedPrefix + ":";
|
||||
} else {
|
||||
prefix = "{" + node.namespaceURI + "}";
|
||||
}
|
||||
return prefix + node.localName;
|
||||
} else {
|
||||
return node.localName;
|
||||
}
|
||||
}
|
||||
|
||||
return promises.resolve(convertNode(document.documentElement));
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
var _ = require("underscore");
|
||||
var xmlbuilder = require("xmlbuilder");
|
||||
|
||||
|
||||
exports.writeString = writeString;
|
||||
|
||||
|
||||
function writeString(root, namespaces) {
|
||||
var uriToPrefix = _.invert(namespaces);
|
||||
|
||||
var nodeWriters = {
|
||||
element: writeElement,
|
||||
text: writeTextNode
|
||||
};
|
||||
|
||||
function writeNode(builder, node) {
|
||||
return nodeWriters[node.type](builder, node);
|
||||
}
|
||||
|
||||
function writeElement(builder, element) {
|
||||
var elementBuilder = builder.element(mapElementName(element.name), element.attributes);
|
||||
element.children.forEach(function(child) {
|
||||
writeNode(elementBuilder, child);
|
||||
});
|
||||
}
|
||||
|
||||
function mapElementName(name) {
|
||||
var longFormMatch = /^\{(.*)\}(.*)$/.exec(name);
|
||||
if (longFormMatch) {
|
||||
var prefix = uriToPrefix[longFormMatch[1]];
|
||||
return prefix + (prefix === "" ? "" : ":") + longFormMatch[2];
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
function writeDocument(root) {
|
||||
var builder = xmlbuilder
|
||||
.create(mapElementName(root.name), {
|
||||
version: '1.0',
|
||||
encoding: 'UTF-8',
|
||||
standalone: true
|
||||
});
|
||||
|
||||
_.forEach(namespaces, function(uri, prefix) {
|
||||
var key = "xmlns" + (prefix === "" ? "" : ":" + prefix);
|
||||
builder.attribute(key, uri);
|
||||
});
|
||||
|
||||
root.children.forEach(function(child) {
|
||||
writeNode(builder, child);
|
||||
});
|
||||
return builder.end();
|
||||
}
|
||||
|
||||
return writeDocument(root);
|
||||
}
|
||||
|
||||
function writeTextNode(builder, node) {
|
||||
builder.text(node.value);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
var xmldom = require("@xmldom/xmldom");
|
||||
var dom = require("@xmldom/xmldom/lib/dom");
|
||||
|
||||
function parseFromString(string) {
|
||||
var error = null;
|
||||
|
||||
var domParser = new xmldom.DOMParser({
|
||||
errorHandler: function(level, message) {
|
||||
error = {level: level, message: message};
|
||||
}
|
||||
});
|
||||
|
||||
var document = domParser.parseFromString(string);
|
||||
|
||||
if (error === null) {
|
||||
return document;
|
||||
} else {
|
||||
throw new Error(error.level + ": " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
exports.parseFromString = parseFromString;
|
||||
exports.Node = dom.Node;
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
var base64js = require("base64-js");
|
||||
var JSZip = require("jszip");
|
||||
|
||||
exports.openArrayBuffer = openArrayBuffer;
|
||||
exports.splitPath = splitPath;
|
||||
exports.joinPath = joinPath;
|
||||
|
||||
function openArrayBuffer(arrayBuffer) {
|
||||
return JSZip.loadAsync(arrayBuffer).then(function(zipFile) {
|
||||
function exists(name) {
|
||||
return zipFile.file(name) !== null;
|
||||
}
|
||||
|
||||
function read(name, encoding) {
|
||||
return zipFile.file(name).async("uint8array").then(function(array) {
|
||||
if (encoding === "base64") {
|
||||
return base64js.fromByteArray(array);
|
||||
} else if (encoding) {
|
||||
var decoder = new TextDecoder(encoding);
|
||||
return decoder.decode(array);
|
||||
} else {
|
||||
return array;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function write(name, contents) {
|
||||
zipFile.file(name, contents);
|
||||
}
|
||||
|
||||
function toArrayBuffer() {
|
||||
return zipFile.generateAsync({type: "arraybuffer"});
|
||||
}
|
||||
|
||||
return {
|
||||
exists: exists,
|
||||
read: read,
|
||||
write: write,
|
||||
toArrayBuffer: toArrayBuffer
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function splitPath(path) {
|
||||
var lastIndex = path.lastIndexOf("/");
|
||||
if (lastIndex === -1) {
|
||||
return {dirname: "", basename: path};
|
||||
} else {
|
||||
return {
|
||||
dirname: path.substring(0, lastIndex),
|
||||
basename: path.substring(lastIndex + 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function joinPath() {
|
||||
var nonEmptyPaths = Array.prototype.filter.call(arguments, function(path) {
|
||||
return path;
|
||||
});
|
||||
|
||||
var relevantPaths = [];
|
||||
|
||||
nonEmptyPaths.forEach(function(path) {
|
||||
if (/^\//.test(path)) {
|
||||
relevantPaths = [path];
|
||||
} else {
|
||||
relevantPaths.push(path);
|
||||
}
|
||||
});
|
||||
|
||||
return relevantPaths.join("/");
|
||||
}
|
||||
+16714
File diff suppressed because it is too large.
Load diff
+13
@@ -0,0 +1,13 @@
|
||||
!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{var g;g="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,g.mammoth=f()}}(function(){var define;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){function Files(){function read(uri){return promises.reject(new Error("could not open external image: '"+uri+"'\ncannot open linked files from a web browser"))}return{read:read}}var promises=require("../../lib/promises");exports.Files=Files},{"../../lib/promises":23}],2:[function(require,module,exports){function openZip(options){return options.arrayBuffer?promises.resolve(zipfile.openArrayBuffer(options.arrayBuffer)):promises.reject(new Error("Could not find file in options"))}var promises=require("../lib/promises"),zipfile=require("../lib/zipfile");exports.openZip=openZip},{"../lib/promises":23,"../lib/zipfile":40}],3:[function(require,module,exports){function DocumentConverter(options){return{convertToHtml:function(element){var comments=_.indexBy(element.type===documents.types.document?element.comments:[],"commentId"),conversion=new DocumentConversion(options,comments);return conversion.convertToHtml(element)}}}function DocumentConversion(options,comments){function convertToHtml(document){var messages=[],html=elementToHtml(document,messages,Object.create(null)),deferredNodes=[];walkHtml(html,function(node){"deferred"===node.type&&deferredNodes.push(node)});var deferredValues=Object.create(null);return promises.forEachSeries(deferredNodes,function(deferred){return deferred.value().then(function(value){deferredValues[deferred.id]=value})}).then(function(){function replaceDeferred(nodes){return flatMap(nodes,function(node){return"deferred"===node.type?deferredValues[node.id]:node.children?[_.extend({},node,{children:replaceDeferred(node.children)})]:[node]})}var writer=writers.writer({prettyPrint:options.prettyPrint,outputFormat:options.outputFormat});return Html.write(writer,Html.simplify(replaceDeferred(html))),new results.Result(writer.asString(),messages)})}function convertElements(elements,messages,context){return flatMap(elements,function(element){return elementToHtml(element,messages,context)})}function elementToHtml(element,messages,context){if(!context)throw new Error("context not set");var handler=elementConverters[element.type];return handler?handler(element,messages,context):[]}function convertParagraph(element,messages,context){return htmlPathForParagraph(element,messages).wrap(function(){var content=convertElements(element.children,messages,context);return ignoreEmptyParagraphs?content:[Html.forceWrite].concat(content)})}function htmlPathForParagraph(element,messages){var style=findStyle(element);return style?style.to:(element.styleId&&messages.push(unrecognisedStyleWarning("paragraph",element)),defaultParagraphStyle)}function convertRun(run,messages,context){var nodes=function(){return convertElements(run.children,messages,context)},paths=[];if(null!==run.highlight){var path=findHtmlPath({type:"highlight",color:run.highlight});path&&paths.push(path)}run.isSmallCaps&&paths.push(findHtmlPathForRunProperty("smallCaps")),run.isAllCaps&&paths.push(findHtmlPathForRunProperty("allCaps")),run.isStrikethrough&&paths.push(findHtmlPathForRunProperty("strikethrough","s")),run.isUnderline&&paths.push(findHtmlPathForRunProperty("underline")),run.verticalAlignment===documents.verticalAlignment.subscript&&paths.push(htmlPaths.element("sub",{},{fresh:!1})),run.verticalAlignment===documents.verticalAlignment.superscript&&paths.push(htmlPaths.element("sup",{},{fresh:!1})),run.isItalic&&paths.push(findHtmlPathForRunProperty("italic","em")),run.isBold&&paths.push(findHtmlPathForRunProperty("bold","strong"));var stylePath=htmlPaths.empty,style=findStyle(run);return style?stylePath=style.to:run.styleId&&messages.push(unrecognisedStyleWarning("run",run)),paths.push(stylePath),paths.forEach(function(path){nodes=path.wrap.bind(path,nodes)}),nodes()}function findHtmlPathForRunProperty(elementType,defaultTagName){var path=findHtmlPath({type:elementType});return path?path:defaultTagName?htmlPaths.element(defaultTagName,{},{fresh:!1}):htmlPaths.empty}function findHtmlPath(element,defaultPath){var style=findStyle(element);return style?style.to:defaultPath}function findStyle(element){for(var i=0;i<styleMap.length;i++)if(styleMap[i].from.matches(element))return styleMap[i]}function recoveringConvertImage(convertImage){return function(image,messages){return prLine truncated
|
||||
"v:imagedata":readImageData,"v:group":readChildElements,"v:rect":readChildElements};return{readXmlElement:readXmlElement,readXmlElements:readXmlElements}}function readNumberingProperties(styleId,element,numbering){var level=element.firstOrEmpty("w:ilvl").attributes["w:val"],numId=element.firstOrEmpty("w:numId").attributes["w:val"];if(void 0!==level&&void 0!==numId)return numbering.findLevel(numId,level);if(null!=styleId){var levelByStyleId=numbering.findLevelByParagraphStyleId(styleId);if(null!=levelByStyleId)return levelByStyleId}return void 0!==numId?numbering.findLevel(numId,"0"):null}function emptyResultWithMessages(messages){return new ReadResult(null,null,messages)}function emptyResult(){return new ReadResult(null)}function elementResult(element){return new ReadResult(element)}function elementResultWithMessages(element,messages){return new ReadResult(element,null,messages)}function ReadResult(element,extra,messages){this.value=element||[],this.extra=extra||[],this._result=new Result({element:this.value,extra:extra},messages),this.messages=this._result.messages}function combineResults(results){var result=Result.combine(_.pluck(results,"_result"));return new ReadResult(_.flatten(_.pluck(result.value,"element")),_.filter(_.flatten(_.pluck(result.value,"extra")),identity),result.messages)}function joinElements(first,second){return _.flatten([first,second])}function identity(value){return value}exports.createBodyReader=createBodyReader,exports._readNumberingProperties=readNumberingProperties;var dingbatToUnicode=require("dingbat-to-unicode"),_=require("underscore"),documents=require("../documents"),Result=require("../results").Result,warning=require("../results").warning,xml=require("../xml"),transforms=require("../transforms"),uris=require("./uris"),supportedImageTypes={"image/png":!0,"image/gif":!0,"image/jpeg":!0,"image/svg+xml":!0,"image/tiff":!0},ignoreElements={"office-word:wrap":!0,"v:shadow":!0,"v:shapetype":!0,"w:annotationRef":!0,"w:bookmarkEnd":!0,"w:sectPr":!0,"w:proofErr":!0,"w:lastRenderedPageBreak":!0,"w:commentRangeStart":!0,"w:commentRangeEnd":!0,"w:del":!0,"w:footnoteRef":!0,"w:endnoteRef":!0,"w:moveFrom":!0,"w:pPr":!0,"w:rPr":!0,"w:tblPr":!0,"w:tblGrid":!0,"w:trPr":!0,"w:tcPr":!0};ReadResult.prototype.toExtra=function(){return new ReadResult(null,joinElements(this.extra,this.value),this.messages)},ReadResult.prototype.insertExtra=function(){var extra=this.extra;return extra&&extra.length?new ReadResult(joinElements(this.value,extra),null,this.messages):this},ReadResult.prototype.map=function(func){var result=this._result.map(function(value){return func(value.element)});return new ReadResult(result.value,this.extra,result.messages)},ReadResult.prototype.flatMap=function(func){var result=this._result.flatMap(function(value){return func(value.element)._result});return new ReadResult(result.value.element,joinElements(this.extra,result.value.extra),result.messages)},ReadResult.map=function(first,second,func){return new ReadResult(func(first.value,second.value),joinElements(first.extra,second.extra),first.messages.concat(second.messages))}},{"../documents":4,"../results":25,"../transforms":30,"../xml":35,"./uris":16,"dingbat-to-unicode":50,underscore:67}],6:[function(require,module,exports){function createCommentsReader(bodyReader){function readCommentsXml(element){return Result.combine(element.getElementsByTagName("w:comment").map(readCommentElement))}function readCommentElement(element){function readOptionalAttribute(name){return(element.attributes[name]||"").trim()||null}var id=element.attributes["w:id"];return bodyReader.readXmlElements(element.children).map(function(body){return documents.comment({commentId:id,body:body,authorName:readOptionalAttribute("w:author"),authorInitials:readOptionalAttribute("w:initials")})})}return readCommentsXml}var documents=require("../documents"),Result=require("../results").Result;exports.createCommentsReader=createCommentsReader},{"../documents":4,"../results":25}],7:[function(require,module,exports){function readContentTypesFromXml(element){var extensionDefaults=Object.create(null),overrides=Object.create(null);return element.children.forEach(function(child){if("content-types:Default"===child.name&&(extensionDefaults[child.attributes.Extension]=child.attributes.ContentType),"content-types:Override"===child.name){var name=child.attributes.PartName;"/"===name.charAt(0)&&(name=name.substring(1)),overrides[name]=child.attributes.ContentType}}),contentTypes(overrides,extensionDefaults)}function contentTypes(overrides,extensionDefaults){return{findContentType:function(path){var overrideContentType=overrides[path];if(overrideContentType)return overrideContentType;var pathParts=path.split("."),extension=pathParts[pathParts.length-1];if(Object.prototype.hasOwnProperty.call(extensionDefaults,extension))return extensionDefaults[extension];var fallback=fallbackContentTypes[extension.toLowerCase()];return fallback?"image/"+fallback:null}}}exports.readContentTypesFroLine truncated
|
||||
externalFileAccess:!1,transformDocument:identity,includeDefaultStyleMap:!0,includeEmbeddedStyleMap:!0}},{underscore:67}],23:[function(require,module,exports){(function(process){function addAlsoToPromise(promise){return promise.also=alsoMethod,promise}function defer(){var resolve,reject,promise=new Promise(function(resolveArg,rejectArg){resolve=resolveArg,reject=rejectArg});return{resolve:resolve,reject:reject,promise:promise}}function ExternalPromise(executor){var promise=new Promise(executor);return promise.__proto__=ExternalPromise.prototype,promise}function toExternalPromise(promise){return ExternalPromise.resolve(promise)}function isNode(){return"undefined"==typeof process?!1:"[object process]"==={}.toString.call(process).toLowerCase()}var _=require("underscore");exports.defer=defer,exports.Promise=Promise,exports.resolve=Promise.resolve.bind(Promise),exports.reject=Promise.reject.bind(Promise),exports.forEachSeries=function(array,func){var arrayIndex=0,deferred=defer(),next=function(){if(arrayIndex<array.length){var element=array[arrayIndex];arrayIndex++,func(element).then(next,deferred.reject)}else deferred.resolve()};return next(),deferred.promise};var props=exports.props=function(obj){var keys=Object.keys(obj),values=Object.values(obj),result=Promise.all(values).then(function(resolvedValues){for(var resolvedObj={},index=0;index<keys.length;index++)resolvedObj[keys[index]]=resolvedValues[index];return resolvedObj});return addAlsoToPromise(result)},alsoMethod=function(func){var result=this.then(function(value){var returnValue=_.extend({},value,func(value));return props(returnValue)});return addAlsoToPromise(result)};exports["try"]=function(func){try{return Promise.resolve(func())}catch(error){return Promise.reject(error)}},ExternalPromise.__proto__=Promise,ExternalPromise.prototype.__proto__=Promise.prototype,ExternalPromise.prototype.done=function(resolve,reject){return this.then(resolve,reject)["catch"](function(error){if(isNode()){var stack=error instanceof Error?error.stack:error;process.stderr.write("Fatal "+stack+"\n"),process.exit(2)}else setTimeout(function(){throw error},0)})},exports.toExternalPromise=toExternalPromise}).call(this,require("_process"))},{_process:66,underscore:67}],24:[function(require,module,exports){function convertElementToRawText(element){if("text"===element.type)return element.value;if(element.type===documents.types.tab)return" ";var tail="paragraph"===element.type?"\n\n":"";return(element.children||[]).map(convertElementToRawText).join("")+tail}var documents=require("./documents");exports.convertElementToRawText=convertElementToRawText},{"./documents":4}],25:[function(require,module,exports){function Result(value,messages){this.value=value,this.messages=messages||[]}function success(value){return new Result(value,[])}function warning(message){return{type:"warning",message:message}}function error(exception){return{type:"error",message:exception.message,error:exception}}function combineMessages(results){var messages=[];return _.flatten(_.pluck(results,"messages"),!0).forEach(function(message){containsMessage(messages,message)||messages.push(message)}),messages}function containsMessage(messages,message){return void 0!==_.find(messages,isSameMessage.bind(null,message))}function isSameMessage(first,second){return first.type===second.type&&first.message===second.message}var _=require("underscore");exports.Result=Result,exports.success=success,exports.warning=warning,exports.error=error,Result.prototype.map=function(func){return new Result(func(this.value),this.messages)},Result.prototype.flatMap=function(func){var funcResult=func(this.value);return new Result(funcResult.value,combineMessages([this,funcResult]))},Result.prototype.flatMapThen=function(func){var that=this;return func(this.value).then(function(otherResult){return new Result(otherResult.value,combineMessages([that,otherResult]))})},Result.combine=function(results){var values=_.flatten(_.pluck(results,"value")),messages=combineMessages(results);return new Result(values,messages)}},{underscore:67}],26:[function(require,module,exports){function readStyle(string){return parseString(styleRule,string)}function createStyleRule(){return lop.rules.sequence(lop.rules.sequence.capture(documentMatcherRule()),lop.rules.tokenOfType("whitespace"),lop.rules.tokenOfType("arrow"),lop.rules.sequence.capture(lop.rules.optional(lop.rules.sequence(lop.rules.tokenOfType("whitespace"),lop.rules.sequence.capture(htmlPathRule())).head())),lop.rules.tokenOfType("end")).map(function(documentMatcher,htmlPath){return{from:documentMatcher,to:htmlPath.valueOrElse(htmlPaths.empty)}})}function readDocumentMatcher(string){return parseString(documentMatcherRule(),string)}function documentMatcherRule(){function createMatcherSuffixesRule(rules){var matcherSuffix=lop.rules.firstOf.apply(lop.rules.firstOf,["matcher suffix"].concat(rules)),matcherSuffixes=lop.rules.zeroOrMore(matcherSuffix);return lop.rules.then(matcherSuffixes,function(suffixes){var maLine truncated
|
||||
}function DOMParser(options){this.options=options||{locator:{}}}function buildErrorHandler(errorImpl,domBuilder,locator){function build(key){var fn=errorImpl[key];!fn&&isCallback&&(fn=2==errorImpl.length?function(msg){errorImpl(key,msg)}:errorImpl),errorHandler[key]=fn&&function(msg){fn("[xmldom "+key+"] "+msg+_locator(locator))}||function(){}}if(!errorImpl){if(domBuilder instanceof DOMHandler)return domBuilder;errorImpl=domBuilder}var errorHandler={},isCallback=errorImpl instanceof Function;return locator=locator||{},build("warning"),build("error"),build("fatalError"),errorHandler}function DOMHandler(){this.cdata=!1}function position(locator,node){node.lineNumber=locator.lineNumber,node.columnNumber=locator.columnNumber}function _locator(l){return l?"\n@"+(l.systemId||"")+"#[line:"+l.lineNumber+",col:"+l.columnNumber+"]":void 0}function _toString(chars,start,length){return"string"==typeof chars?chars.substr(start,length):chars.length>=start+length||start?new java.lang.String(chars,start,length)+"":chars}function appendElement(hander,node){hander.currentElement?hander.currentElement.appendChild(node):hander.doc.appendChild(node)}var conventions=require("./conventions"),dom=require("./dom"),entities=require("./entities"),sax=require("./sax"),DOMImplementation=dom.DOMImplementation,NAMESPACE=conventions.NAMESPACE,ParseError=sax.ParseError,XMLReader=sax.XMLReader;DOMParser.prototype.parseFromString=function(source,mimeType){var options=this.options,sax=new XMLReader,domBuilder=options.domBuilder||new DOMHandler,errorHandler=options.errorHandler,locator=options.locator,defaultNSMap=options.xmlns||{},isHTML=/\/x?html?$/.test(mimeType),entityMap=isHTML?entities.HTML_ENTITIES:entities.XML_ENTITIES;locator&&domBuilder.setDocumentLocator(locator),sax.errorHandler=buildErrorHandler(errorHandler,domBuilder,locator),sax.domBuilder=options.domBuilder||domBuilder,isHTML&&(defaultNSMap[""]=NAMESPACE.HTML),defaultNSMap.xml=defaultNSMap.xml||NAMESPACE.XML;var normalize=options.normalizeLineEndings||normalizeLineEndings;return source&&"string"==typeof source?sax.parse(normalize(source),defaultNSMap,entityMap):sax.errorHandler.error("invalid doc source"),domBuilder.doc},DOMHandler.prototype={startDocument:function(){this.doc=(new DOMImplementation).createDocument(null,null,null),this.locator&&(this.doc.documentURI=this.locator.systemId)},startElement:function(namespaceURI,localName,qName,attrs){var doc=this.doc,el=doc.createElementNS(namespaceURI,qName||localName),len=attrs.length;appendElement(this,el),this.currentElement=el,this.locator&&position(this.locator,el);for(var i=0;len>i;i++){var namespaceURI=attrs.getURI(i),value=attrs.getValue(i),qName=attrs.getQName(i),attr=doc.createAttributeNS(namespaceURI,qName);this.locator&&position(attrs.getLocator(i),attr),attr.value=attr.nodeValue=value,el.setAttributeNode(attr)}},endElement:function(namespaceURI,localName,qName){var current=this.currentElement;current.tagName;this.currentElement=current.parentNode},startPrefixMapping:function(prefix,uri){},endPrefixMapping:function(prefix){},processingInstruction:function(target,data){var ins=this.doc.createProcessingInstruction(target,data);this.locator&&position(this.locator,ins),appendElement(this,ins)},ignorableWhitespace:function(ch,start,length){},characters:function(chars,start,length){if(chars=_toString.apply(this,arguments)){if(this.cdata)var charNode=this.doc.createCDATASection(chars);else var charNode=this.doc.createTextNode(chars);this.currentElement?this.currentElement.appendChild(charNode):/^\s*$/.test(chars)&&this.doc.appendChild(charNode),this.locator&&position(this.locator,charNode)}},skippedEntity:function(name){},endDocument:function(){this.doc.normalize()},setDocumentLocator:function(locator){(this.locator=locator)&&(locator.lineNumber=0)},comment:function(chars,start,length){chars=_toString.apply(this,arguments);var comm=this.doc.createComment(chars);this.locator&&position(this.locator,comm),appendElement(this,comm)},startCDATA:function(){this.cdata=!0},endCDATA:function(){this.cdata=!1},startDTD:function(name,publicId,systemId){var impl=this.doc.implementation;if(impl&&impl.createDocumentType){var dt=impl.createDocumentType(name,publicId,systemId);this.locator&&position(this.locator,dt),appendElement(this,dt),this.doc.doctype=dt}},warning:function(error){console.warn("[xmldom warning] "+error,_locator(this.locator))},error:function(error){console.error("[xmldom error] "+error,_locator(this.locator))},fatalError:function(error){throw new ParseError(error,this.locator)}},"endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){DOMHandler.prototype[key]=function(){return null}}),exports.__DOMHandler=DOMHandler,exports.normalizeLineEndings=normalizeLineEndings,exports.DOMParser=DOMParser},{"./conventions":41,"./dom":43,"./entities":44,"./sax":46}],43:[function(require,module,exports){funcLine truncated
|
||||
CharacterData.prototype={data:"",substringData:function(offset,count){return this.data.substring(offset,offset+count)},appendData:function(text){text=this.data+text,this.nodeValue=this.data=text,this.length=text.length},insertData:function(offset,text){this.replaceData(offset,0,text)},appendChild:function(newChild){throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR])},deleteData:function(offset,count){this.replaceData(offset,count,"")},replaceData:function(offset,count,text){var start=this.data.substring(0,offset),end=this.data.substring(offset+count);text=start+text+end,this.nodeValue=this.data=text,this.length=text.length}},_extends(CharacterData,Node),Text.prototype={nodeName:"#text",nodeType:TEXT_NODE,splitText:function(offset){var text=this.data,newText=text.substring(offset);text=text.substring(0,offset),this.data=this.nodeValue=text,this.length=text.length;var newNode=this.ownerDocument.createTextNode(newText);return this.parentNode&&this.parentNode.insertBefore(newNode,this.nextSibling),newNode}},_extends(Text,CharacterData),Comment.prototype={nodeName:"#comment",nodeType:COMMENT_NODE},_extends(Comment,CharacterData),CDATASection.prototype={nodeName:"#cdata-section",nodeType:CDATA_SECTION_NODE},_extends(CDATASection,CharacterData),DocumentType.prototype.nodeType=DOCUMENT_TYPE_NODE,_extends(DocumentType,Node),Notation.prototype.nodeType=NOTATION_NODE,_extends(Notation,Node),Entity.prototype.nodeType=ENTITY_NODE,_extends(Entity,Node),EntityReference.prototype.nodeType=ENTITY_REFERENCE_NODE,_extends(EntityReference,Node),DocumentFragment.prototype.nodeName="#document-fragment",DocumentFragment.prototype.nodeType=DOCUMENT_FRAGMENT_NODE,_extends(DocumentFragment,Node),ProcessingInstruction.prototype.nodeType=PROCESSING_INSTRUCTION_NODE,_extends(ProcessingInstruction,Node),XMLSerializer.prototype.serializeToString=function(node,isHtml,nodeFilter){return nodeSerializeToString.call(node,isHtml,nodeFilter)},Node.prototype.toString=nodeSerializeToString;try{Object.defineProperty&&(Object.defineProperty(LiveNodeList.prototype,"length",{get:function(){return _updateLiveList(this),this.$$length}}),Object.defineProperty(Node.prototype,"textContent",{get:function(){return getTextContent(this)},set:function(data){switch(this.nodeType){case ELEMENT_NODE:case DOCUMENT_FRAGMENT_NODE:for(;this.firstChild;)this.removeChild(this.firstChild);(data||String(data))&&this.appendChild(this.ownerDocument.createTextNode(data));break;default:this.data=data,this.value=data,this.nodeValue=data}}}),__set__=function(object,key,value){object["$$"+key]=value})}catch(e){}exports.DocumentType=DocumentType,exports.DOMException=DOMException,exports.DOMImplementation=DOMImplementation,exports.Element=Element,exports.Node=Node,exports.NodeList=NodeList,exports.XMLSerializer=XMLSerializer},{"./conventions":41}],44:[function(require,module,exports){var freeze=require("./conventions").freeze;exports.XML_ENTITIES=freeze({amp:"&",apos:"'",gt:">",lt:"<",quot:'"'}),exports.HTML_ENTITIES=freeze({lt:"<",gt:">",amp:"&",quot:'"',apos:"'",Agrave:"À",Aacute:"Á",Acirc:"Â",Atilde:"Ã",Auml:"Ä",Aring:"Å",AElig:"Æ",Ccedil:"Ç",Egrave:"È",Eacute:"É",Ecirc:"Ê",Euml:"Ë",Igrave:"Ì",Iacute:"Í",Icirc:"Î",Iuml:"Ï",ETH:"Ð",Ntilde:"Ñ",Ograve:"Ò",Oacute:"Ó",Ocirc:"Ô",Otilde:"Õ",Ouml:"Ö",Oslash:"Ø",Ugrave:"Ù",Uacute:"Ú",Ucirc:"Û",Uuml:"Ü",Yacute:"Ý",THORN:"Þ",szlig:"ß",agrave:"à",aacute:"á",acirc:"â",atilde:"ã",auml:"ä",aring:"å",aelig:"æ",ccedil:"ç",egrave:"è",eacute:"é",ecirc:"ê",euml:"ë",igrave:"ì",iacute:"í",icirc:"î",iuml:"ï",eth:"ð",ntilde:"ñ",ograve:"ò",oacute:"ó",ocirc:"ô",otilde:"õ",ouml:"ö",oslash:"ø",ugrave:"ù",uacute:"ú",ucirc:"û",uuml:"ü",yacute:"ý",thorn:"þ",yuml:"ÿ",nbsp:" ",iexcl:"¡",cent:"¢",pound:"£",curren:"¤",yen:"¥",brvbar:"¦",sect:"§",uml:"¨",copy:"©",ordf:"ª",laquo:"«",not:"¬",shy:"",reg:"®",macr:"¯",deg:"°",plusmn:"±",sup2:"²",sup3:"³",acute:"´",micro:"µ",para:"¶",middot:"·",cedil:"¸",sup1:"¹",ordm:"º",raquo:"»",frac14:"¼",frac12:"½",frac34:"¾",iquest:"¿",times:"×",divide:"÷",forall:"∀",part:"∂",exist:"∃",empty:"∅",nabla:"∇",isin:"∈",notin:"∉",ni:"∋",prod:"∏",sum:"∑",minus:"−",lowast:"∗",radic:"√",prop:"∝",infin:"∞",ang:"∠",and:"∧",or:"∨",cap:"∩",cup:"∪","int":"∫",there4:"∴",sim:"∼",cong:"≅",asymp:"≈",ne:"≠",equiv:"≡",le:"≤",ge:"≥",sub:"⊂",sup:"⊃",nsub:"⊄",sube:"⊆",supe:"⊇",oplus:"⊕",otimes:"⊗",perp:"⊥",sdot:"⋅",Alpha:"Α",Beta:"Β",Gamma:"Γ",Delta:"Δ",Epsilon:"Ε",Zeta:"Ζ",Eta:"Η",Theta:"Θ",Iota:"Ι",Kappa:"Κ",Lambda:"Λ",Mu:"Μ",Nu:"Ν",Xi:"Ξ",Omicron:"Ο",Pi:"Π",Rho:"Ρ",Sigma:"Σ",Tau:"Τ",Upsilon:"Υ",Phi:"Φ",Chi:"Χ",Psi:"Ψ",Omega:"Ω",alpha:"α",beta:"β",gamma:"γ",delta:"δ",epsilon:"ε",zeta:"ζ",eta:"η",theta:"θ",iota:"ι",kappa:"κ",lambda:"λ",mu:"μ",nu:"ν",xi:"ξ",omicron:"ο",pi:"π",rho:"ρ",sigmaf:"ς",sigma:"σ",tau:"τ",upsilon:"υ",phi:"Line truncated
|
||||
return noAssert||checkIEEE754(buf,value,offset,8,1.7976931348623157e308,-1.7976931348623157e308),ieee754.write(buf,value,offset,littleEndian,52,8),offset+8}function base64clean(str){if(str=stringtrim(str).replace(INVALID_BASE64_RE,""),str.length<2)return"";for(;str.length%4!==0;)str+="=";return str}function stringtrim(str){return str.trim?str.trim():str.replace(/^\s+|\s+$/g,"")}function toHex(n){return 16>n?"0"+n.toString(16):n.toString(16)}function utf8ToBytes(string,units){units=units||1/0;for(var codePoint,length=string.length,leadSurrogate=null,bytes=[],i=0;length>i;++i){if(codePoint=string.charCodeAt(i),codePoint>55295&&57344>codePoint){if(!leadSurrogate){if(codePoint>56319){(units-=3)>-1&&bytes.push(239,191,189);continue}if(i+1===length){(units-=3)>-1&&bytes.push(239,191,189);continue}leadSurrogate=codePoint;continue}if(56320>codePoint){(units-=3)>-1&&bytes.push(239,191,189),leadSurrogate=codePoint;continue}codePoint=(leadSurrogate-55296<<10|codePoint-56320)+65536}else leadSurrogate&&(units-=3)>-1&&bytes.push(239,191,189);if(leadSurrogate=null,128>codePoint){if((units-=1)<0)break;bytes.push(codePoint)}else if(2048>codePoint){if((units-=2)<0)break;bytes.push(codePoint>>6|192,63&codePoint|128)}else if(65536>codePoint){if((units-=3)<0)break;bytes.push(codePoint>>12|224,codePoint>>6&63|128,63&codePoint|128)}else{if(!(1114112>codePoint))throw new Error("Invalid code point");if((units-=4)<0)break;bytes.push(codePoint>>18|240,codePoint>>12&63|128,codePoint>>6&63|128,63&codePoint|128)}}return bytes}function asciiToBytes(str){for(var byteArray=[],i=0;i<str.length;++i)byteArray.push(255&str.charCodeAt(i));return byteArray}function utf16leToBytes(str,units){for(var c,hi,lo,byteArray=[],i=0;i<str.length&&!((units-=2)<0);++i)c=str.charCodeAt(i),hi=c>>8,lo=c%256,byteArray.push(lo),byteArray.push(hi);return byteArray}function base64ToBytes(str){return base64.toByteArray(base64clean(str))}function blitBuffer(src,dst,offset,length){for(var i=0;length>i&&!(i+offset>=dst.length||i>=src.length);++i)dst[i+offset]=src[i];return i}function isnan(val){return val!==val}var base64=require("base64-js"),ieee754=require("ieee754"),isArray=require("isarray");exports.Buffer=Buffer,exports.SlowBuffer=SlowBuffer,exports.INSPECT_MAX_BYTES=50,Buffer.TYPED_ARRAY_SUPPORT=void 0!==global.TYPED_ARRAY_SUPPORT?global.TYPED_ARRAY_SUPPORT:typedArraySupport(),exports.kMaxLength=kMaxLength(),Buffer.poolSize=8192,Buffer._augment=function(arr){return arr.__proto__=Buffer.prototype,arr},Buffer.from=function(value,encodingOrOffset,length){return from(null,value,encodingOrOffset,length)},Buffer.TYPED_ARRAY_SUPPORT&&(Buffer.prototype.__proto__=Uint8Array.prototype,Buffer.__proto__=Uint8Array,"undefined"!=typeof Symbol&&Symbol.species&&Buffer[Symbol.species]===Buffer&&Object.defineProperty(Buffer,Symbol.species,{value:null,configurable:!0})),Buffer.alloc=function(size,fill,encoding){return alloc(null,size,fill,encoding)},Buffer.allocUnsafe=function(size){return allocUnsafe(null,size)},Buffer.allocUnsafeSlow=function(size){return allocUnsafe(null,size)},Buffer.isBuffer=function(b){return!(null==b||!b._isBuffer)},Buffer.compare=function(a,b){if(!Buffer.isBuffer(a)||!Buffer.isBuffer(b))throw new TypeError("Arguments must be Buffers");if(a===b)return 0;for(var x=a.length,y=b.length,i=0,len=Math.min(x,y);len>i;++i)if(a[i]!==b[i]){x=a[i],y=b[i];break}return y>x?-1:x>y?1:0},Buffer.isEncoding=function(encoding){switch(String(encoding).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},Buffer.concat=function(list,length){if(!isArray(list))throw new TypeError('"list" argument must be an Array of Buffers');if(0===list.length)return Buffer.alloc(0);var i;if(void 0===length)for(length=0,i=0;i<list.length;++i)length+=list[i].length;var buffer=Buffer.allocUnsafe(length),pos=0;for(i=0;i<list.length;++i){var buf=list[i];if(!Buffer.isBuffer(buf))throw new TypeError('"list" argument must be an Array of Buffers');buf.copy(buffer,pos),pos+=buf.length}return buffer},Buffer.byteLength=byteLength,Buffer.prototype._isBuffer=!0,Buffer.prototype.swap16=function(){var len=this.length;if(len%2!==0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(var i=0;len>i;i+=2)swap(this,i,i+1);return this},Buffer.prototype.swap32=function(){var len=this.length;if(len%4!==0)throw new RangeError("Buffer size must be a multiple of 32-bits");for(var i=0;len>i;i+=4)swap(this,i,i+3),swap(this,i+1,i+2);return this},Buffer.prototype.swap64=function(){var len=this.length;if(len%8!==0)throw new RangeError("Buffer size must be a multiple of 64-bits");for(var i=0;len>i;i+=8)swap(this,i,i+7),swap(this,i+1,i+6),swap(this,i+2,i+5),swap(this,i+3,i+4);return this},Buffer.prototype.toString=function(){var length=0|this.length;return 0===length?"":0===arguments.length?utf8Slice(this,0,length):slowToString.apply(this,arguments)},Buffer.prototype.equals=function(b){iLine truncated
|
||||
}(function(){return function s(a,o,h){function u(r,t){if(!o[r]){if(!a[r]){var e="function"==typeof require&&require;if(!t&&e)return e(r,!0);if(l)return l(r,!0);var i=new Error("Cannot find module '"+r+"'");throw i.code="MODULE_NOT_FOUND",i}var n=o[r]={exports:{}};a[r][0].call(n.exports,function(t){var e=a[r][1][t];return u(e||t)},n,n.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,t=0;t<h.length;t++)u(h[t]);return u}({1:[function(t,e,r){"use strict";var c=t("./utils"),d=t("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(t){for(var e,r,i,n,s,a,o,h=[],u=0,l=t.length,f=l,d="string"!==c.getTypeOf(t);u<t.length;)f=l-u,i=d?(e=t[u++],r=l>u?t[u++]:0,l>u?t[u++]:0):(e=t.charCodeAt(u++),r=l>u?t.charCodeAt(u++):0,l>u?t.charCodeAt(u++):0),n=e>>2,s=(3&e)<<4|r>>4,a=f>1?(15&r)<<2|i>>6:64,o=f>2?63&i:64,h.push(p.charAt(n)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(t){var e,r,i,n,s,a,o=0,h=0,u="data:";if(t.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(t=t.replace(/[^A-Za-z0-9\+\/\=]/g,"")).length/4;if(t.charAt(t.length-1)===p.charAt(64)&&f--,t.charAt(t.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=d.uint8array?new Uint8Array(0|f):new Array(0|f);o<t.length;)e=p.indexOf(t.charAt(o++))<<2|(n=p.indexOf(t.charAt(o++)))>>4,r=(15&n)<<4|(s=p.indexOf(t.charAt(o++)))>>2,i=(3&s)<<6|(a=p.indexOf(t.charAt(o++))),l[h++]=e,64!==s&&(l[h++]=r),64!==a&&(l[h++]=i);return l}},{"./support":30,"./utils":32}],2:[function(t,e,r){"use strict";function o(t,e,r,i,n){this.compressedSize=t,this.uncompressedSize=e,this.crc32=r,this.compression=i,this.compressedContent=n}var i=t("./external"),n=t("./stream/DataWorker"),s=t("./stream/Crc32Probe"),a=t("./stream/DataLengthProbe");o.prototype={getContentWorker:function(){var t=new n(i.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),e=this;return t.on("end",function(){if(this.streamInfo.data_length!==e.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),t},getCompressedWorker:function(){return new n(i.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(t,e,r){return t.pipe(new s).pipe(new a("uncompressedSize")).pipe(e.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",e)},e.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(t,e,r){"use strict";var i=t("./stream/GenericWorker");r.STORE={magic:"\x00\x00",compressWorker:function(t){return new i("STORE compression")},uncompressWorker:function(){return new i("STORE decompression")}},r.DEFLATE=t("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(t,e,r){"use strict";var i=t("./utils"),o=function(){for(var t,e=[],r=0;256>r;r++){t=r;for(var i=0;8>i;i++)t=1&t?3988292384^t>>>1:t>>>1;e[r]=t}return e}();e.exports=function(t,e){return void 0!==t&&t.length?"string"!==i.getTypeOf(t)?function(t,e,r,i){var n=o,s=i+r;t^=-1;for(var a=i;s>a;a++)t=t>>>8^n[255&(t^e[a])];return-1^t}(0|e,t,t.length,0):function(t,e,r,i){var n=o,s=i+r;t^=-1;for(var a=i;s>a;a++)t=t>>>8^n[255&(t^e.charCodeAt(a))];return-1^t}(0|e,t,t.length,0):0}},{"./utils":32}],5:[function(t,e,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(t,e,r){"use strict";var i=null;i="undefined"!=typeof Promise?Promise:t("lie"),e.exports={Promise:i}},{lie:37}],7:[function(t,e,r){"use strict";function h(t,e){a.call(this,"FlateWorker/"+t),this._pako=null,this._pakoAction=t,this._pakoOptions=e,this.meta={}}var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,n=t("pako"),s=t("./utils"),a=t("./stream/GenericWorker"),o=i?"uint8array":"array";r.magic="\b\x00",s.inherits(h,a),h.prototype.processChunk=function(t){this.meta=t.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,t.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new n[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var e=this;this._pako.onData=function(t){e.push({data:t,meta:e.meta})}},r.compressWorker=function(t){return new h("Deflate",t)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(t,e,r){"use strict";function A(t,e){var r,i="";fLine truncated
|
||||
for(;s>n;)"array"===e||"nodebuffer"===e?i.push(String.fromCharCode.apply(null,t.slice(n,Math.min(n+r,s)))):i.push(String.fromCharCode.apply(null,t.subarray(n,Math.min(n+r,s)))),n+=r;return i.join("")},stringifyByChar:function(t){for(var e="",r=0;r<t.length;r++)e+=String.fromCharCode(t[r]);return e},applyCanBeUsed:{uint8array:function(){try{return o.uint8array&&1===String.fromCharCode.apply(null,new Uint8Array(1)).length}catch(t){return!1}}(),nodebuffer:function(){try{return o.nodebuffer&&1===String.fromCharCode.apply(null,r.allocBuffer(1)).length}catch(t){return!1}}()}};a.applyFromCharCode=f;var c={};c.string={string:n,array:function(t){return l(t,new Array(t.length))},arraybuffer:function(t){return c.string.uint8array(t).buffer},uint8array:function(t){return l(t,new Uint8Array(t.length))},nodebuffer:function(t){return l(t,r.allocBuffer(t.length))}},c.array={string:f,array:n,arraybuffer:function(t){return new Uint8Array(t).buffer},uint8array:function(t){return new Uint8Array(t)},nodebuffer:function(t){return r.newBufferFrom(t)}},c.arraybuffer={string:function(t){return f(new Uint8Array(t))},array:function(t){return d(new Uint8Array(t),new Array(t.byteLength))},arraybuffer:n,uint8array:function(t){return new Uint8Array(t)},nodebuffer:function(t){return r.newBufferFrom(new Uint8Array(t))}},c.uint8array={string:f,array:function(t){return d(t,new Array(t.length))},arraybuffer:function(t){return t.buffer},uint8array:n,nodebuffer:function(t){return r.newBufferFrom(t)}},c.nodebuffer={string:f,array:function(t){return d(t,new Array(t.length))},arraybuffer:function(t){return c.nodebuffer.uint8array(t).buffer},uint8array:function(t){return d(t,new Uint8Array(t.length))},nodebuffer:n},a.transformTo=function(t,e){if(e=e||"",!t)return e;a.checkSupport(t);var r=a.getTypeOf(e);return c[r][t](e)},a.getTypeOf=function(t){return"string"==typeof t?"string":"[object Array]"===Object.prototype.toString.call(t)?"array":o.nodebuffer&&r.isBuffer(t)?"nodebuffer":o.uint8array&&t instanceof Uint8Array?"uint8array":o.arraybuffer&&t instanceof ArrayBuffer?"arraybuffer":void 0},a.checkSupport=function(t){if(!o[t.toLowerCase()])throw new Error(t+" is not supported by this platform")},a.MAX_VALUE_16BITS=65535,a.MAX_VALUE_32BITS=-1,a.pretty=function(t){var e,r,i="";for(r=0;r<(t||"").length;r++)i+="\\x"+((e=t.charCodeAt(r))<16?"0":"")+e.toString(16).toUpperCase();return i},a.delay=function(t,e,r){i(function(){t.apply(r||null,e||[])})},a.inherits=function(t,e){function r(){}r.prototype=e.prototype,t.prototype=new r},a.extend=function(){var t,e,r={};for(t=0;t<arguments.length;t++)for(e in arguments[t])arguments[t].hasOwnProperty(e)&&void 0===r[e]&&(r[e]=arguments[t][e]);return r},a.prepareContent=function(r,t,i,n,s){return u.Promise.resolve(t).then(function(i){return o.blob&&(i instanceof Blob||-1!==["[object File]","[object Blob]"].indexOf(Object.prototype.toString.call(i)))&&"undefined"!=typeof FileReader?new u.Promise(function(e,r){var t=new FileReader;t.onload=function(t){e(t.target.result)},t.onerror=function(t){r(t.target.error)},t.readAsArrayBuffer(i)}):i}).then(function(t){var e=a.getTypeOf(t);return e?("arraybuffer"===e?t=a.transformTo("uint8array",t):"string"===e&&(s?t=h.decode(t):i&&!0!==n&&(t=function(t){return l(t,o.uint8array?new Uint8Array(t.length):new Array(t.length))}(t))),t):u.Promise.reject(new Error("Can't read the data of '"+r+"'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?"))})}},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,"set-immediate-shim":54}],33:[function(t,e,r){"use strict";function h(t){this.files=[],this.loadOptions=t}var i=t("./reader/readerFor"),n=t("./utils"),s=t("./signature"),a=t("./zipEntry"),o=(t("./utf8"),t("./support"));h.prototype={checkSignature:function(t){if(!this.reader.readAndCheckSignature(t)){this.reader.index-=4;var e=this.reader.readString(4);throw new Error("Corrupted zip or bug: unexpected signature ("+n.pretty(e)+", expected "+n.pretty(t)+")")}},isSignature:function(t,e){var r=this.reader.index;this.reader.setIndex(t);var i=this.reader.readString(4)===e;return this.reader.setIndex(r),i},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2);var t=this.reader.readData(this.zipCommentLength),e=o.uint8array?"uint8array":"array",r=n.transformTo(e,t);this.zipComment=this.loadOptions.decodeFileName(r)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),this.reader.skip(4),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centrLine truncated
|
||||
this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new d.Buf16(2*w),this.dyn_dtree=new d.Buf16(2*(2*a+1)),this.bl_tree=new d.Buf16(2*(2*o+1)),D(this.dyn_ltree),D(this.dyn_dtree),D(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new d.Buf16(k+1),this.heap=new d.Buf16(2*s+1),D(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new d.Buf16(2*s+1),D(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function G(t){var e;return t&&t.state?(t.total_in=t.total_out=0,t.data_type=n,(e=t.state).pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=e.wrap?C:E,t.adler=2===e.wrap?0:1,e.last_flush=l,u._tr_init(e),m):R(t,_)}function K(t){var e=G(t);return e===m&&function(t){t.window_size=2*t.w_size,D(t.head),t.max_lazy_match=h[t.level].max_lazy,t.good_match=h[t.level].good_length,t.nice_match=h[t.level].nice_length,t.max_chain_length=h[t.level].max_chain,t.strstart=0,t.block_start=0,t.lookahead=0,t.insert=0,t.match_length=t.prev_length=x-1,t.match_available=0,t.ins_h=0}(t.state),e}function Y(t,e,r,i,n,s){if(!t)return _;var a=1;if(e===g&&(e=6),0>i?(a=0,i=-i):i>15&&(a=2,i-=16),1>n||n>y||r!==v||8>i||i>15||0>e||e>9||0>s||s>b)return R(t,_);8===i&&(i=9);var o=new H;return(t.state=o).strm=t,o.wrap=a,o.gzhead=null,o.w_bits=i,o.w_size=1<<o.w_bits,o.w_mask=o.w_size-1,o.hash_bits=n+7,o.hash_size=1<<o.hash_bits,o.hash_mask=o.hash_size-1,o.hash_shift=~~((o.hash_bits+x-1)/x),o.window=new d.Buf8(2*o.w_size),o.head=new d.Buf16(o.hash_size),o.prev=new d.Buf16(o.w_size),o.lit_bufsize=1<<n+6,o.pending_buf_size=4*o.lit_bufsize,o.pending_buf=new d.Buf8(o.pending_buf_size),o.d_buf=1*o.lit_bufsize,o.l_buf=3*o.lit_bufsize,o.level=e,o.strategy=s,o.method=r,K(t)}var h,d=t("../utils/common"),u=t("./trees"),c=t("./adler32"),p=t("./crc32"),i=t("./messages"),l=0,f=4,m=0,_=-2,g=-1,b=4,n=2,v=8,y=9,s=286,a=30,o=19,w=2*s+1,k=15,x=3,S=258,z=S+x+1,C=42,E=113,A=1,I=2,O=3,B=4;h=[new M(0,0,0,0,function(t,e){var r=65535;for(r>t.pending_buf_size-5&&(r=t.pending_buf_size-5);;){if(t.lookahead<=1){if(j(t),0===t.lookahead&&e===l)return A;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var i=t.block_start+r;if((0===t.strstart||t.strstart>=i)&&(t.lookahead=t.strstart-i,t.strstart=i,N(t,!1),0===t.strm.avail_out))return A;if(t.strstart-t.block_start>=t.w_size-z&&(N(t,!1),0===t.strm.avail_out))return A}return t.insert=0,e===f?(N(t,!0),0===t.strm.avail_out?O:B):(t.strstart>t.block_start&&(N(t,!1),t.strm.avail_out),A)}),new M(4,4,8,4,Z),new M(4,5,16,8,Z),new M(4,6,32,32,Z),new M(4,4,16,16,W),new M(8,16,32,32,W),new M(8,16,128,128,W),new M(8,32,128,256,W),new M(32,128,258,1024,W),new M(32,258,258,4096,W)],r.deflateInit=function(t,e){return Y(t,e,v,15,8,0)},r.deflateInit2=Y,r.deflateReset=K,r.deflateResetKeep=G,r.deflateSetHeader=function(t,e){return t&&t.state?2!==t.state.wrap?_:(t.state.gzhead=e,m):_},r.deflate=function(t,e){var r,i,n,s;if(!t||!t.state||e>5||0>e)return t?R(t,_):_;if(i=t.state,!t.output||!t.input&&0!==t.avail_in||666===i.status&&e!==f)return R(t,0===t.avail_out?-5:_);if(i.strm=t,r=i.last_flush,i.last_flush=e,i.status===C)if(2===i.wrap)t.adler=0,U(i,31),U(i,139),U(i,8),i.gzhead?(U(i,(i.gzhead.text?1:0)+(i.gzhead.hcrc?2:0)+(i.gzhead.extra?4:0)+(i.gzhead.name?8:0)+(i.gzhead.comment?16:0)),U(i,255&i.gzhead.time),U(i,i.gzhead.time>>8&255),U(i,i.gzhead.time>>16&255),U(i,i.gzhead.time>>24&255),U(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),U(i,255&i.gzhead.os),i.gzhead.extra&&i.gzhead.extra.length&&(U(i,255&i.gzhead.extra.length),U(i,i.gzhead.extra.length>>8&255)),i.gzhead.hcrc&&(t.adler=p(t.adler,i.pending_buf,i.pending,0)),i.gzindex=0,i.status=69):(U(i,0),U(i,0),U(i,0),U(i,0),U(i,0),U(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),U(i,3),i.status=E);else{var a=v+(i.w_bits-8<<4)<<8;a|=(2<=i.strategy||i.level<2?0:i.level<6?1:6===i.level?2:3)<<6,0!==i.strstart&&(a|=32),a+=31-a%31,i.status=E,P(i,a),0!==i.strstart&&(P(i,t.adler>>>16),P(i,65535&t.adler)),t.adler=1}if(69===i.status)if(i.gzhead.extra){for(n=i.pending;i.gzindex<(65535&i.gzhead.extra.length)&&(i.pending!==i.pending_buf_size||(i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),F(t),n=i.pending,i.pending!==i.pending_buf_size));)U(i,255&i.gzhead.extra[i.gzindex]),i.gzindex++;i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),i.gzindex===i.gzhead.extra.length&&(i.gzindex=0,i.status=73)}else i.status=73;if(73===i.status)if(i.gzhead.name){n=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),F(t),n=i.pending,i.pending===i.pendingLine truncated
|
||||
var self={asString:function(){return string},range:function(startIndex,endIndex){return new StringSourceRange(string,description,startIndex,endIndex)}};return self},function(string,description,startIndex,endIndex){this._string=string,this._description=description,this._startIndex=startIndex,this._endIndex=endIndex});StringSourceRange.prototype.to=function(otherRange){return new StringSourceRange(this._string,this._description,this._startIndex,otherRange._endIndex)},StringSourceRange.prototype.describe=function(){var position=this._position(),description=this._description?this._description+"\n":"";return description+"Line number: "+position.lineNumber+"\nCharacter number: "+position.characterNumber},StringSourceRange.prototype.lineNumber=function(){return this._position().lineNumber},StringSourceRange.prototype.characterNumber=function(){return this._position().characterNumber},StringSourceRange.prototype._position=function(){for(var self=this,index=0,nextNewLine=function(){return self._string.indexOf("\n",index)},lineNumber=1;-1!==nextNewLine()&&nextNewLine()<this._startIndex;)index=nextNewLine()+1,lineNumber+=1;var characterNumber=this._startIndex-index+1;return{lineNumber:lineNumber,characterNumber:characterNumber}}},{}],56:[function(require,module,exports){module.exports=function(name,value,source){this.name=name,this.value=value,source&&(this.source=source)}},{}],57:[function(require,module,exports){var TokenIterator=module.exports=function(tokens,startIndex){this._tokens=tokens,this._startIndex=startIndex||0};TokenIterator.prototype.head=function(){return this._tokens[this._startIndex]},TokenIterator.prototype.tail=function(startIndex){return new TokenIterator(this._tokens,this._startIndex+1)},TokenIterator.prototype.toArray=function(){return this._tokens.slice(this._startIndex)},TokenIterator.prototype.end=function(){return this._tokens[this._tokens.length-1]},TokenIterator.prototype.to=function(end){var start=this.head().source,endToken=end.head()||end.end();return start.to(endToken.source)}},{}],58:[function(require,module,exports){function InfixRules(infixRules){function untilExclusive(name){return new InfixRules(infixRules.slice(0,ruleNames().indexOf(name)))}function untilInclusive(name){return new InfixRules(infixRules.slice(0,ruleNames().indexOf(name)+1))}function ruleNames(){return infixRules.map(function(rule){return rule.name})}function apply(leftResult){for(var currentResult,source;;){if(currentResult=applyToTokens(leftResult.remaining()),!currentResult.isSuccess())return currentResult.isFailure()?leftResult:currentResult;source=leftResult.source().to(currentResult.source()),leftResult=results.success(currentResult.value()(leftResult.value(),source),currentResult.remaining(),source)}}function applyToTokens(tokens){return rules.firstOf("infix",infixRules.map(function(infix){return infix.rule}))(tokens)}return{apply:apply,untilExclusive:untilExclusive,untilInclusive:untilInclusive}}var rules=require("./rules"),results=require("./parsing-results");exports.parser=function(name,prefixRules,infixRuleBuilders){function createInfixRule(infixRuleBuilder){return{name:infixRuleBuilder.name,rule:lazyRule(infixRuleBuilder.ruleBuilder.bind(null,self))}}function rule(){return createRule(infixRules)}function leftAssociative(name){return createRule(infixRules.untilExclusive(name))}function rightAssociative(name){return createRule(infixRules.untilInclusive(name))}function createRule(infixRules){return apply.bind(null,infixRules)}function apply(infixRules,tokens){var leftResult=prefixRule(tokens);return leftResult.isSuccess()?infixRules.apply(leftResult):leftResult}var self={rule:rule,leftAssociative:leftAssociative,rightAssociative:rightAssociative},infixRules=new InfixRules(infixRuleBuilders.map(createInfixRule)),prefixRule=rules.firstOf(name,prefixRules);return self},exports.infix=function(name,ruleBuilder){function map(func){return exports.infix(name,function(parser){var rule=ruleBuilder(parser);return function(tokens){var result=rule(tokens);return result.map(function(right){return function(left,source){return func(left,right,source)}})}})}return{name:name,ruleBuilder:ruleBuilder,map:map}};var lazyRule=function(ruleBuilder){var rule;return function(input){return rule||(rule=ruleBuilder()),rule(input)}}},{"./parsing-results":62,"./rules":64}],59:[function(require,module,exports){exports.error=function(options){return new Error(options)};var Error=function(options){this.expected=options.expected,this.actual=options.actual,this._location=options.location};Error.prototype.describe=function(){var locationDescription=this._location?this._location.describe()+":\n":"";return locationDescription+"Expected "+this.expected+"\nbut got "+this.actual},Error.prototype.lineNumber=function(){return this._location.lineNumber()},Error.prototype.characterNumber=function(){return this._location.characterNumber()}},{}],60:[function(require,module,exports){var LazyIterator=(exports.fromArray=function(array){var index=0,hasNextLine truncated
|
||||
idx=output.length;else for(var j=0,len=value.length;len>j;)output[idx++]=value[j++];else strict||(output[idx++]=value)}return output}function memoize(func,hasher){var memoize=function(key){var cache=memoize.cache,address=""+(hasher?hasher.apply(this,arguments):key);return has$1(cache,address)||(cache[address]=func.apply(this,arguments)),cache[address]};return memoize.cache={},memoize}function throttle(func,wait,options){var timeout,context,args,result,previous=0;options||(options={});var later=function(){previous=options.leading===!1?0:now(),timeout=null,result=func.apply(context,args),timeout||(context=args=null)},throttled=function(){var _now=now();previous||options.leading!==!1||(previous=_now);var remaining=wait-(_now-previous);return context=this,args=arguments,0>=remaining||remaining>wait?(timeout&&(clearTimeout(timeout),timeout=null),previous=_now,result=func.apply(context,args),timeout||(context=args=null)):timeout||options.trailing===!1||(timeout=setTimeout(later,remaining)),result};return throttled.cancel=function(){clearTimeout(timeout),previous=0,timeout=context=args=null},throttled}function debounce(func,wait,immediate){var timeout,previous,args,result,context,later=function(){var passed=now()-previous;wait>passed?timeout=setTimeout(later,wait-passed):(timeout=null,immediate||(result=func.apply(context,args)),timeout||(args=context=null))},debounced=restArguments(function(_args){return context=this,args=_args,previous=now(),timeout||(timeout=setTimeout(later,wait),immediate&&(result=func.apply(context,args))),result});return debounced.cancel=function(){clearTimeout(timeout),timeout=args=context=null},debounced}function wrap(func,wrapper){return partial(wrapper,func)}function negate(predicate){return function(){return!predicate.apply(this,arguments)}}function compose(){var args=arguments,start=args.length-1;return function(){for(var i=start,result=args[start].apply(this,arguments);i--;)result=args[i].call(this,result);return result}}function after(times,func){return function(){return--times<1?func.apply(this,arguments):void 0}}function before(times,func){var memo;return function(){return--times>0&&(memo=func.apply(this,arguments)),1>=times&&(func=null),memo}}function findKey(obj,predicate,context){predicate=cb(predicate,context);for(var key,_keys=keys(obj),i=0,length=_keys.length;length>i;i++)if(key=_keys[i],predicate(obj[key],key,obj))return key}function createPredicateIndexFinder(dir){return function(array,predicate,context){predicate=cb(predicate,context);for(var length=getLength(array),index=dir>0?0:length-1;index>=0&&length>index;index+=dir)if(predicate(array[index],index,array))return index;return-1}}function sortedIndex(array,obj,iteratee,context){iteratee=cb(iteratee,context,1);for(var value=iteratee(obj),low=0,high=getLength(array);high>low;){var mid=Math.floor((low+high)/2);iteratee(array[mid])<value?low=mid+1:high=mid}return low}function createIndexFinder(dir,predicateFind,sortedIndex){return function(array,item,idx){var i=0,length=getLength(array);if("number"==typeof idx)dir>0?i=idx>=0?idx:Math.max(idx+length,i):length=idx>=0?Math.min(idx+1,length):idx+length+1;else if(sortedIndex&&idx&&length)return idx=sortedIndex(array,item),array[idx]===item?idx:-1;if(item!==item)return idx=predicateFind(slice.call(array,i,length),isNaN$1),idx>=0?idx+i:-1;for(idx=dir>0?i:length-1;idx>=0&&length>idx;idx+=dir)if(array[idx]===item)return idx;return-1}}function find(obj,predicate,context){var keyFinder=isArrayLike(obj)?findIndex:findKey,key=keyFinder(obj,predicate,context);return void 0!==key&&-1!==key?obj[key]:void 0}function findWhere(obj,attrs){return find(obj,matcher(attrs))}function each(obj,iteratee,context){iteratee=optimizeCb(iteratee,context);var i,length;if(isArrayLike(obj))for(i=0,length=obj.length;length>i;i++)iteratee(obj[i],i,obj);else{var _keys=keys(obj);for(i=0,length=_keys.length;length>i;i++)iteratee(obj[_keys[i]],_keys[i],obj)}return obj}function map(obj,iteratee,context){iteratee=cb(iteratee,context);for(var _keys=!isArrayLike(obj)&&keys(obj),length=(_keys||obj).length,results=Array(length),index=0;length>index;index++){var currentKey=_keys?_keys[index]:index;results[index]=iteratee(obj[currentKey],currentKey,obj)}return results}function createReduce(dir){var reducer=function(obj,iteratee,memo,initial){var _keys=!isArrayLike(obj)&&keys(obj),length=(_keys||obj).length,index=dir>0?0:length-1;for(initial||(memo=obj[_keys?_keys[index]:index],index+=dir);index>=0&&length>index;index+=dir){var currentKey=_keys?_keys[index]:index;memo=iteratee(memo,obj[currentKey],currentKey,obj)}return memo};return function(obj,iteratee,memo,context){var initial=arguments.length>=3;return reducer(obj,optimizeCb(iteratee,context,4),memo,initial)}}function filter(obj,predicate,context){var results=[];return predicate=cb(predicate,context),each(obj,function(value,index,list){predicate(value,index,list)&&results.push(value)}),results}function reject(obj,predicate,context){return filter(obj,negate(cb(predicLine truncated
|
||||
this.name="!DOCTYPE",this.documentObject=parent,isObject(pubID)&&(ref=pubID,pubID=ref.pubID,sysID=ref.sysID),null==sysID&&(ref1=[pubID,sysID],sysID=ref1[0],pubID=ref1[1]),null!=pubID&&(this.pubID=this.stringify.dtdPubID(pubID)),null!=sysID&&(this.sysID=this.stringify.dtdSysID(sysID))}return extend(XMLDocType,superClass),XMLDocType.prototype.element=function(name,value){var child;return child=new XMLDTDElement(this,name,value),this.children.push(child),this},XMLDocType.prototype.attList=function(elementName,attributeName,attributeType,defaultValueType,defaultValue){var child;return child=new XMLDTDAttList(this,elementName,attributeName,attributeType,defaultValueType,defaultValue),this.children.push(child),this},XMLDocType.prototype.entity=function(name,value){var child;return child=new XMLDTDEntity(this,!1,name,value),this.children.push(child),this},XMLDocType.prototype.pEntity=function(name,value){var child;return child=new XMLDTDEntity(this,!0,name,value),this.children.push(child),this},XMLDocType.prototype.notation=function(name,value){var child;return child=new XMLDTDNotation(this,name,value),this.children.push(child),this},XMLDocType.prototype.toString=function(options){return this.options.writer.set(options).docType(this)},XMLDocType.prototype.ele=function(name,value){return this.element(name,value)},XMLDocType.prototype.att=function(elementName,attributeName,attributeType,defaultValueType,defaultValue){return this.attList(elementName,attributeName,attributeType,defaultValueType,defaultValue)},XMLDocType.prototype.ent=function(name,value){return this.entity(name,value)},XMLDocType.prototype.pent=function(name,value){return this.pEntity(name,value)},XMLDocType.prototype.not=function(name,value){return this.notation(name,value)},XMLDocType.prototype.up=function(){return this.root()||this.documentObject},XMLDocType}(XMLNode)}).call(this)},{"./Utility":68,"./XMLDTDAttList":72,"./XMLDTDElement":73,"./XMLDTDEntity":74,"./XMLDTDNotation":75,"./XMLNode":81}],78:[function(require,module,exports){(function(){var XMLDocument,XMLNode,XMLStringWriter,XMLStringifier,isPlainObject,extend=function(child,parent){function ctor(){this.constructor=child}for(var key in parent)hasProp.call(parent,key)&&(child[key]=parent[key]);return ctor.prototype=parent.prototype,child.prototype=new ctor,child.__super__=parent.prototype,child},hasProp={}.hasOwnProperty;isPlainObject=require("./Utility").isPlainObject,XMLNode=require("./XMLNode"),XMLStringifier=require("./XMLStringifier"),XMLStringWriter=require("./XMLStringWriter"),module.exports=XMLDocument=function(superClass){function XMLDocument(options){XMLDocument.__super__.constructor.call(this,null),this.name="?xml",options||(options={}),options.writer||(options.writer=new XMLStringWriter),this.options=options,this.stringify=new XMLStringifier(options),this.isDocument=!0}return extend(XMLDocument,superClass),XMLDocument.prototype.end=function(writer){var writerOptions;return writer?isPlainObject(writer)&&(writerOptions=writer,writer=this.options.writer.set(writerOptions)):writer=this.options.writer,writer.document(this)},XMLDocument.prototype.toString=function(options){return this.options.writer.set(options).document(this)},XMLDocument}(XMLNode)}).call(this)},{"./Utility":68,"./XMLNode":81,"./XMLStringWriter":85,"./XMLStringifier":86}],79:[function(require,module,exports){(function(){var XMLAttribute,XMLCData,XMLComment,XMLDTDAttList,XMLDTDElement,XMLDTDEntity,XMLDTDNotation,XMLDeclaration,XMLDocType,XMLDocumentCB,XMLElement,XMLProcessingInstruction,XMLRaw,XMLStringWriter,XMLStringifier,XMLText,getValue,isFunction,isObject,isPlainObject,ref,hasProp={}.hasOwnProperty;ref=require("./Utility"),isObject=ref.isObject,isFunction=ref.isFunction,isPlainObject=ref.isPlainObject,getValue=ref.getValue,XMLElement=require("./XMLElement"),XMLCData=require("./XMLCData"),XMLComment=require("./XMLComment"),XMLRaw=require("./XMLRaw"),XMLText=require("./XMLText"),XMLProcessingInstruction=require("./XMLProcessingInstruction"),XMLDeclaration=require("./XMLDeclaration"),XMLDocType=require("./XMLDocType"),XMLDTDAttList=require("./XMLDTDAttList"),XMLDTDEntity=require("./XMLDTDEntity"),XMLDTDElement=require("./XMLDTDElement"),XMLDTDNotation=require("./XMLDTDNotation"),XMLAttribute=require("./XMLAttribute"),XMLStringifier=require("./XMLStringifier"),XMLStringWriter=require("./XMLStringWriter"),module.exports=XMLDocumentCB=function(){function XMLDocumentCB(options,onData,onEnd){var writerOptions;this.name="?xml",options||(options={}),options.writer?isPlainObject(options.writer)&&(writerOptions=options.writer,options.writer=new XMLStringWriter(writerOptions)):options.writer=new XMLStringWriter(options),this.options=options,this.writer=options.writer,this.stringify=new XMLStringifier(options),this.onDataCallback=onData||function(){},this.onEndCallback=onEnd||function(){},this.currentNode=null,this.currentLevel=-1,this.openTags={},this.documentStarted=!1,this.documentCompleted=!1,this.root=null}return XMLDocuLine truncated
|
||||
this.attribute(att));if(0===node.children.length||node.children.every(function(e){return""===e.value}))this.allowEmpty?this.stream.write("></"+node.name+">"):this.stream.write(this.spacebeforeslash+"/>");else if(this.pretty&&1===node.children.length&&null!=node.children[0].value)this.stream.write(">"),this.stream.write(node.children[0].value),this.stream.write("</"+node.name+">");else{for(this.stream.write(">"+this.newline),ref1=node.children,i=0,len=ref1.length;len>i;i++)switch(child=ref1[i],!1){case!(child instanceof XMLCData):this.cdata(child,level+1);break;case!(child instanceof XMLComment):this.comment(child,level+1);break;case!(child instanceof XMLElement):this.element(child,level+1);break;case!(child instanceof XMLRaw):this.raw(child,level+1);break;case!(child instanceof XMLText):this.text(child,level+1);break;case!(child instanceof XMLProcessingInstruction):this.processingInstruction(child,level+1);break;default:throw new Error("Unknown XML node type: "+child.constructor.name)}this.stream.write(space+"</"+node.name+">")}return this.stream.write(this.endline(node))},XMLStreamWriter.prototype.processingInstruction=function(node,level){return this.stream.write(this.space(level)+"<?"+node.target),node.value&&this.stream.write(" "+node.value),this.stream.write(this.spacebeforeslash+"?>"+this.endline(node))},XMLStreamWriter.prototype.raw=function(node,level){return this.stream.write(this.space(level)+node.value+this.endline(node))},XMLStreamWriter.prototype.text=function(node,level){return this.stream.write(this.space(level)+node.value+this.endline(node))},XMLStreamWriter.prototype.dtdAttList=function(node,level){return this.stream.write(this.space(level)+"<!ATTLIST "+node.elementName+" "+node.attributeName+" "+node.attributeType),"#DEFAULT"!==node.defaultValueType&&this.stream.write(" "+node.defaultValueType),node.defaultValue&&this.stream.write(' "'+node.defaultValue+'"'),this.stream.write(this.spacebeforeslash+">"+this.endline(node))},XMLStreamWriter.prototype.dtdElement=function(node,level){return this.stream.write(this.space(level)+"<!ELEMENT "+node.name+" "+node.value),this.stream.write(this.spacebeforeslash+">"+this.endline(node))},XMLStreamWriter.prototype.dtdEntity=function(node,level){return this.stream.write(this.space(level)+"<!ENTITY"),node.pe&&this.stream.write(" %"),this.stream.write(" "+node.name),node.value?this.stream.write(' "'+node.value+'"'):(node.pubID&&node.sysID?this.stream.write(' PUBLIC "'+node.pubID+'" "'+node.sysID+'"'):node.sysID&&this.stream.write(' SYSTEM "'+node.sysID+'"'),node.nData&&this.stream.write(" NDATA "+node.nData)),this.stream.write(this.spacebeforeslash+">"+this.endline(node))},XMLStreamWriter.prototype.dtdNotation=function(node,level){return this.stream.write(this.space(level)+"<!NOTATION "+node.name),node.pubID&&node.sysID?this.stream.write(' PUBLIC "'+node.pubID+'" "'+node.sysID+'"'):node.pubID?this.stream.write(' PUBLIC "'+node.pubID+'"'):node.sysID&&this.stream.write(' SYSTEM "'+node.sysID+'"'),this.stream.write(this.spacebeforeslash+">"+this.endline(node))},XMLStreamWriter.prototype.endline=function(node){return node.isLastRootNode?"":this.newline},XMLStreamWriter}(XMLWriterBase)}).call(this)},{"./XMLCData":70,"./XMLComment":71,"./XMLDTDAttList":72,"./XMLDTDElement":73,"./XMLDTDEntity":74,"./XMLDTDNotation":75,"./XMLDeclaration":76,"./XMLDocType":77,"./XMLElement":80,"./XMLProcessingInstruction":82,"./XMLRaw":83,"./XMLText":87,"./XMLWriterBase":88}],85:[function(require,module,exports){(function(){var XMLCData,XMLComment,XMLDTDAttList,XMLDTDElement,XMLDTDEntity,XMLDTDNotation,XMLDeclaration,XMLDocType,XMLElement,XMLProcessingInstruction,XMLRaw,XMLStringWriter,XMLText,XMLWriterBase,extend=function(child,parent){function ctor(){this.constructor=child}for(var key in parent)hasProp.call(parent,key)&&(child[key]=parent[key]);return ctor.prototype=parent.prototype,child.prototype=new ctor,child.__super__=parent.prototype,child},hasProp={}.hasOwnProperty;XMLDeclaration=require("./XMLDeclaration"),XMLDocType=require("./XMLDocType"),XMLCData=require("./XMLCData"),XMLComment=require("./XMLComment"),XMLElement=require("./XMLElement"),XMLRaw=require("./XMLRaw"),XMLText=require("./XMLText"),XMLProcessingInstruction=require("./XMLProcessingInstruction"),XMLDTDAttList=require("./XMLDTDAttList"),XMLDTDElement=require("./XMLDTDElement"),XMLDTDEntity=require("./XMLDTDEntity"),XMLDTDNotation=require("./XMLDTDNotation"),XMLWriterBase=require("./XMLWriterBase"),module.exports=XMLStringWriter=function(superClass){function XMLStringWriter(options){XMLStringWriter.__super__.constructor.call(this,options)}return extend(XMLStringWriter,superClass),XMLStringWriter.prototype.document=function(doc){var child,i,len,r,ref;for(this.textispresent=!1,r="",ref=doc.children,i=0,len=ref.length;len>i;i++)child=ref[i],r+=function(){switch(!1){case!(child instanceof XMLDeclaration):return this.declaration(child);case!(child instanceof XMLDocType):return this.docType(child);case!(child instanceofLine truncated
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "mammoth",
|
||||
"version": "1.13.0",
|
||||
"author": "Michael Williamson <mike@zwobble.org>",
|
||||
"description": "Convert Word documents from docx to simple HTML and Markdown",
|
||||
"keywords": [
|
||||
"docx",
|
||||
"html",
|
||||
"office",
|
||||
"word",
|
||||
"markdown",
|
||||
"md"
|
||||
],
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mwilliamson/mammoth.js.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.8.6",
|
||||
"argparse": "~1.0.3",
|
||||
"base64-js": "^1.5.1",
|
||||
"dingbat-to-unicode": "^1.0.1",
|
||||
"jszip": "^3.7.1",
|
||||
"lop": "^0.4.2",
|
||||
"underscore": "^1.13.1",
|
||||
"xmlbuilder": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.2.5",
|
||||
"browserify": "~13.0.1",
|
||||
"browserify-prepend-licenses": "~1.0.0",
|
||||
"duck": "^0.1.12",
|
||||
"eslint": "2.13.1",
|
||||
"hamjest": "^4.0.1",
|
||||
"mocha": "~2.2.5",
|
||||
"temp": "^0.9.4",
|
||||
"typescript": "^5.0.4",
|
||||
"uglify-js": "~2.4.8"
|
||||
},
|
||||
"browser": {
|
||||
"./lib/unzip.js": "./browser/unzip.js",
|
||||
"./lib/docx/files.js": "./browser/docx/files.js"
|
||||
},
|
||||
"bin": {
|
||||
"mammoth": "bin/mammoth"
|
||||
},
|
||||
"scripts": {
|
||||
"pretest": "eslint lib test",
|
||||
"test": "mocha 'test/**/*.tests.js'",
|
||||
"check-typescript": "tsc --noEmit lib/index.d.ts",
|
||||
"prepare": "make mammoth.browser.min.js"
|
||||
},
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "26.8.2"
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"globals": {
|
||||
"__dirname": false,
|
||||
"describe": false,
|
||||
"it": false,
|
||||
"setTimeout": false
|
||||
}
|
||||
}
|
||||
+892
@@ -0,0 +1,892 @@
|
||||
var assert = require("assert");
|
||||
var promises = require("../lib/promises");
|
||||
|
||||
var documents = require("../lib/documents");
|
||||
var documentToHtml = require("../lib/document-to-html");
|
||||
var DocumentConverter = documentToHtml.DocumentConverter;
|
||||
var commentAuthorLabel = documentToHtml.commentAuthorLabel;
|
||||
var test = require("./test")(module);
|
||||
var htmlPaths = require("../lib/styles/html-paths");
|
||||
var xml = require("../lib/xml");
|
||||
var results = require("../lib/results");
|
||||
var documentMatchers = require("../lib/styles/document-matchers");
|
||||
var Html = require("../lib/html");
|
||||
|
||||
|
||||
test('should empty document to empty string', function() {
|
||||
var document = new documents.Document([]);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, "");
|
||||
});
|
||||
});
|
||||
|
||||
test('should convert document containing one paragraph to single p element', function() {
|
||||
var document = new documents.Document([
|
||||
paragraphOfText("Hello.")
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, "<p>Hello.</p>");
|
||||
});
|
||||
});
|
||||
|
||||
test('ignores empty paragraphs', function() {
|
||||
var document = new documents.Document([
|
||||
paragraphOfText("")
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, "");
|
||||
});
|
||||
});
|
||||
|
||||
test('text is HTML-escaped', function() {
|
||||
var document = new documents.Document([
|
||||
paragraphOfText("1 < 2")
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, "<p>1 < 2</p>");
|
||||
});
|
||||
});
|
||||
|
||||
test('should convert document containing multiple paragraphs to multiple p elements', function() {
|
||||
var document = new documents.Document([
|
||||
paragraphOfText("Hello."),
|
||||
paragraphOfText("Goodbye.")
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, "<p>Hello.</p><p>Goodbye.</p>");
|
||||
});
|
||||
});
|
||||
|
||||
test('uses style mappings to pick HTML element for docx paragraph', function() {
|
||||
var document = new documents.Document([
|
||||
paragraphOfText("Hello.", "Heading1", "Heading 1")
|
||||
]);
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.paragraph({styleName: documentMatchers.equalTo("Heading 1")}),
|
||||
to: htmlPaths.topLevelElement("h1")
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, "<h1>Hello.</h1>");
|
||||
});
|
||||
});
|
||||
|
||||
test('mappings for style names are case insensitive', function() {
|
||||
var document = new documents.Document([
|
||||
paragraphOfText("Hello.", "Heading1", "heading 1")
|
||||
]);
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.paragraph({styleName: documentMatchers.equalTo("Heading 1")}),
|
||||
to: htmlPaths.topLevelElement("h1")
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, "<h1>Hello.</h1>");
|
||||
});
|
||||
});
|
||||
|
||||
test('can use non-default HTML element for unstyled paragraphs', function() {
|
||||
var document = new documents.Document([
|
||||
paragraphOfText("Hello.")
|
||||
]);
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.paragraph(),
|
||||
to: htmlPaths.topLevelElement("h1")
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, "<h1>Hello.</h1>");
|
||||
});
|
||||
});
|
||||
|
||||
test('warning is emitted if paragraph style is unrecognised', function() {
|
||||
var document = new documents.Document([
|
||||
paragraphOfText("Hello.", "Heading1", "Heading 1")
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.deepEqual(result.messages, [results.warning("Unrecognised paragraph style: 'Heading 1' (Style ID: Heading1)")]);
|
||||
});
|
||||
});
|
||||
|
||||
test('can use stacked styles to generate nested HTML elements', function() {
|
||||
var document = new documents.Document([
|
||||
paragraphOfText("Hello.")
|
||||
]);
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.paragraph(),
|
||||
to: htmlPaths.elements(["h1", "span"])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, "<h1><span>Hello.</span></h1>");
|
||||
});
|
||||
});
|
||||
|
||||
test('bold runs are wrapped in <strong> tags by default', function() {
|
||||
var run = runOfText("Hello.", {isBold: true});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<strong>Hello.</strong>");
|
||||
});
|
||||
});
|
||||
|
||||
test('bold runs can be configured with style mapping', function() {
|
||||
var run = runOfText("Hello.", {isBold: true});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.bold,
|
||||
to: htmlPaths.elements([htmlPaths.element("em")])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<em>Hello.</em>");
|
||||
});
|
||||
});
|
||||
|
||||
test('bold runs can exist inside other tags', function() {
|
||||
var run = new documents.Paragraph([
|
||||
runOfText("Hello.", {isBold: true})
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<p><strong>Hello.</strong></p>");
|
||||
});
|
||||
});
|
||||
|
||||
test('consecutive bold runs are wrapped in a single <strong> element', function() {
|
||||
var paragraph = new documents.Paragraph([
|
||||
runOfText("Hello", {isBold: true}),
|
||||
runOfText(".", {isBold: true})
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(paragraph).then(function(result) {
|
||||
assert.equal(result.value, "<p><strong>Hello.</strong></p>");
|
||||
});
|
||||
});
|
||||
|
||||
test('underline runs are ignored by default', function() {
|
||||
var run = runOfText("Hello.", {isUnderline: true});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "Hello.");
|
||||
});
|
||||
});
|
||||
|
||||
test('underline runs can be mapped using style mapping', function() {
|
||||
var run = runOfText("Hello.", {isUnderline: true});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.underline,
|
||||
to: htmlPaths.elements([htmlPaths.element("u")])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<u>Hello.</u>");
|
||||
});
|
||||
});
|
||||
|
||||
test('style mapping for underline runs does not close parent elements', function() {
|
||||
var run = runOfText("Hello.", {isUnderline: true, isBold: true});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.underline,
|
||||
to: htmlPaths.elements([htmlPaths.element("u")])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<strong><u>Hello.</u></strong>");
|
||||
});
|
||||
});
|
||||
|
||||
test('strikethrough runs are wrapped in <s> tags by default', function() {
|
||||
var run = runOfText("Hello.", {isStrikethrough: true});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<s>Hello.</s>");
|
||||
});
|
||||
});
|
||||
|
||||
test('strikethrough runs can be configured with style mapping', function() {
|
||||
var run = runOfText("Hello.", {isStrikethrough: true});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.strikethrough,
|
||||
to: htmlPaths.elements([htmlPaths.element("del")])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<del>Hello.</del>");
|
||||
});
|
||||
});
|
||||
|
||||
test('italic runs are wrapped in <em> tags', function() {
|
||||
var run = runOfText("Hello.", {isItalic: true});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<em>Hello.</em>");
|
||||
});
|
||||
});
|
||||
|
||||
test('italic runs can be configured with style mapping', function() {
|
||||
var run = runOfText("Hello.", {isItalic: true});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.italic,
|
||||
to: htmlPaths.elements([htmlPaths.element("strong")])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<strong>Hello.</strong>");
|
||||
});
|
||||
});
|
||||
|
||||
test('run can be both bold and italic', function() {
|
||||
var run = runOfText("Hello.", {isBold: true, isItalic: true});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<strong><em>Hello.</em></strong>");
|
||||
});
|
||||
});
|
||||
|
||||
test('superscript runs are wrapped in <sup> tags', function() {
|
||||
var run = runOfText("Hello.", {
|
||||
verticalAlignment: documents.verticalAlignment.superscript
|
||||
});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<sup>Hello.</sup>");
|
||||
});
|
||||
});
|
||||
|
||||
test('subscript runs are wrapped in <sub> tags', function() {
|
||||
var run = runOfText("Hello.", {
|
||||
verticalAlignment: documents.verticalAlignment.subscript
|
||||
});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<sub>Hello.</sub>");
|
||||
});
|
||||
});
|
||||
|
||||
test('all caps runs are ignored by default', function() {
|
||||
var run = runOfText("Hello.", {isAllCaps: true});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "Hello.");
|
||||
});
|
||||
});
|
||||
|
||||
test('all caps runs can be configured with style mapping', function() {
|
||||
var run = runOfText("Hello.", {isAllCaps: true});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.allCaps,
|
||||
to: htmlPaths.elements([htmlPaths.element("span")])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<span>Hello.</span>");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('small caps runs are ignored by default', function() {
|
||||
var run = runOfText("Hello.", {isSmallCaps: true});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "Hello.");
|
||||
});
|
||||
});
|
||||
|
||||
test('small caps runs can be configured with style mapping', function() {
|
||||
var run = runOfText("Hello.", {isSmallCaps: true});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.smallCaps,
|
||||
to: htmlPaths.elements([htmlPaths.element("span")])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<span>Hello.</span>");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('highlighted runs are ignored by default', function() {
|
||||
var run = runOfText("Hello.", {highlight: "yellow"});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "Hello.");
|
||||
});
|
||||
});
|
||||
|
||||
test('highlighted runs can be configured with style mapping for all highlights', function() {
|
||||
var run = runOfText("Hello.", {highlight: "yellow"});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.highlight(null),
|
||||
to: htmlPaths.elements([htmlPaths.element("mark")])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<mark>Hello.</mark>");
|
||||
});
|
||||
});
|
||||
|
||||
test('highlighted runs can be configured with style mapping for specific highlight color', function() {
|
||||
var paragraph = new documents.Paragraph([
|
||||
runOfText("Yellow", {highlight: "yellow"}),
|
||||
runOfText("Red", {highlight: "red"})
|
||||
]);
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.highlight({color: "yellow"}),
|
||||
to: htmlPaths.elements([htmlPaths.element("mark", {"class": "yellow"})])
|
||||
},
|
||||
{
|
||||
from: documentMatchers.highlight({color: undefined}),
|
||||
to: htmlPaths.elements([htmlPaths.element("mark")])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(paragraph).then(function(result) {
|
||||
assert.equal(result.value, '<p><mark class="yellow">Yellow</mark><mark>Red</mark></p>');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('run styles are converted to HTML if mapping exists', function() {
|
||||
var run = runOfText("Hello.", {styleId: "Heading1Char", styleName: "Heading 1 Char"});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.run({styleName: documentMatchers.equalTo("Heading 1 Char")}),
|
||||
to: htmlPaths.elements(["strong"])
|
||||
}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, "<strong>Hello.</strong>");
|
||||
});
|
||||
});
|
||||
|
||||
test('warning is emitted if run style is unrecognised', function() {
|
||||
var run = runOfText("Hello.", {styleId: "Heading1Char", styleName: "Heading 1 Char"});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.deepEqual(result.messages, [results.warning("Unrecognised run style: 'Heading 1 Char' (Style ID: Heading1Char)")]);
|
||||
});
|
||||
});
|
||||
|
||||
test('docx hyperlink is converted to <a>', function() {
|
||||
var hyperlink = new documents.Hyperlink(
|
||||
[runOfText("Hello.")],
|
||||
{href: "http://www.example.com"}
|
||||
);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(hyperlink).then(function(result) {
|
||||
assert.equal(result.value, '<a href="http://www.example.com">Hello.</a>');
|
||||
});
|
||||
});
|
||||
|
||||
test('docx hyperlink can be collapsed', function() {
|
||||
var hyperlink = new documents.Document([
|
||||
new documents.Hyperlink(
|
||||
[runOfText("Hello ")],
|
||||
{href: "http://www.example.com"}
|
||||
),
|
||||
new documents.Hyperlink(
|
||||
[runOfText("world")],
|
||||
{href: "http://www.example.com"}
|
||||
)
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(hyperlink).then(function(result) {
|
||||
assert.equal(result.value, '<a href="http://www.example.com">Hello world</a>');
|
||||
});
|
||||
});
|
||||
|
||||
test('docx hyperlink with anchor is converted to <a>', function() {
|
||||
var hyperlink = new documents.Hyperlink(
|
||||
[runOfText("Hello.")],
|
||||
{anchor: "_Peter"}
|
||||
);
|
||||
var converter = new DocumentConverter({
|
||||
idPrefix: "doc-42-"
|
||||
});
|
||||
return converter.convertToHtml(hyperlink).then(function(result) {
|
||||
assert.equal(result.value, '<a href="#doc-42-_Peter">Hello.</a>');
|
||||
});
|
||||
});
|
||||
|
||||
test('hyperlink target frame is used as anchor target', function() {
|
||||
var hyperlink = new documents.Hyperlink(
|
||||
[runOfText("Hello.")],
|
||||
{anchor: "start", targetFrame: "_blank"}
|
||||
);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(hyperlink).then(function(result) {
|
||||
assert.equal(result.value, '<a href="#start" target="_blank">Hello.</a>');
|
||||
});
|
||||
});
|
||||
|
||||
test('unchecked checkbox is converted to unchecked checkbox input', function() {
|
||||
var checkbox = documents.checkbox({checked: false});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(checkbox).then(function(result) {
|
||||
assert.equal(result.value, '<input type="checkbox" />');
|
||||
});
|
||||
});
|
||||
|
||||
test('checked checkbox is converted to checked checkbox input', function() {
|
||||
var checkbox = documents.checkbox({checked: true});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(checkbox).then(function(result) {
|
||||
assert.equal(result.value, '<input type="checkbox" checked="checked" />');
|
||||
});
|
||||
});
|
||||
|
||||
test('bookmarks are converted to anchors', function() {
|
||||
var bookmarkStart = new documents.BookmarkStart({name: "_Peter"});
|
||||
var converter = new DocumentConverter({
|
||||
idPrefix: "doc-42-"
|
||||
});
|
||||
var document = new documents.Document([bookmarkStart]);
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, '<a id="doc-42-_Peter"></a>');
|
||||
});
|
||||
});
|
||||
|
||||
test('docx tab is converted to tab in HTML', function() {
|
||||
var tab = new documents.Tab();
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(tab).then(function(result) {
|
||||
assert.equal(result.value, "\t");
|
||||
});
|
||||
});
|
||||
|
||||
test('docx table is converted to table in HTML', function() {
|
||||
var table = new documents.Table([
|
||||
new documents.TableRow([
|
||||
new documents.TableCell([paragraphOfText("Top left")]),
|
||||
new documents.TableCell([paragraphOfText("Top right")])
|
||||
]),
|
||||
new documents.TableRow([
|
||||
new documents.TableCell([paragraphOfText("Bottom left")]),
|
||||
new documents.TableCell([paragraphOfText("Bottom right")])
|
||||
])
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(table).then(function(result) {
|
||||
var expectedHtml = "<table>" +
|
||||
"<tr><td><p>Top left</p></td><td><p>Top right</p></td></tr>" +
|
||||
"<tr><td><p>Bottom left</p></td><td><p>Bottom right</p></td></tr>" +
|
||||
"</table>";
|
||||
assert.equal(result.value, expectedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
test('table style mappings can be used to map tables', function() {
|
||||
var table = new documents.Table([], {styleName: "Normal Table"});
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.table({styleName: documentMatchers.equalTo("Normal Table")}),
|
||||
to: htmlPaths.topLevelElement("table", {"class": "normal-table"})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return converter.convertToHtml(table).then(function(result) {
|
||||
var expectedHtml = '<table class="normal-table"></table>';
|
||||
assert.equal(result.value, expectedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
test('header rows are wrapped in thead', function() {
|
||||
var table = new documents.Table([
|
||||
new documents.TableRow([new documents.TableCell([])], {isHeader: true}),
|
||||
new documents.TableRow([new documents.TableCell([])], {isHeader: true}),
|
||||
new documents.TableRow([new documents.TableCell([])], {isHeader: false})
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(table).then(function(result) {
|
||||
var expectedHtml = "<table>" +
|
||||
"<thead><tr><th></th></tr><tr><th></th></tr></thead>" +
|
||||
"<tbody><tr><td></td></tr></tbody>" +
|
||||
"</table>";
|
||||
assert.equal(result.value, expectedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
test('tbody is omitted if all rows are headers', function() {
|
||||
var table = new documents.Table([
|
||||
new documents.TableRow([new documents.TableCell([])], {isHeader: true})
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(table).then(function(result) {
|
||||
var expectedHtml = "<table>" +
|
||||
"<thead><tr><th></th></tr></thead>" +
|
||||
"</table>";
|
||||
assert.equal(result.value, expectedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
test('unexpected table children do not cause error', function() {
|
||||
var table = new documents.Table([
|
||||
new documents.tab()
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(table).then(function(result) {
|
||||
var expectedHtml = "<table>\t</table>";
|
||||
assert.equal(result.value, expectedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
test('empty cells are preserved in table', function() {
|
||||
var table = new documents.Table([
|
||||
new documents.TableRow([
|
||||
new documents.TableCell([paragraphOfText("")]),
|
||||
new documents.TableCell([paragraphOfText("Top right")])
|
||||
])
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(table).then(function(result) {
|
||||
var expectedHtml = "<table>" +
|
||||
"<tr><td></td><td><p>Top right</p></td></tr>" +
|
||||
"</table>";
|
||||
assert.equal(result.value, expectedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
test('empty rows are preserved in table', function() {
|
||||
var table = new documents.Table([
|
||||
new documents.TableRow([
|
||||
new documents.TableCell([paragraphOfText("Row 1")])
|
||||
]),
|
||||
new documents.TableRow([])
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(table).then(function(result) {
|
||||
var expectedHtml = "<table>" +
|
||||
"<tr><td><p>Row 1</p></td></tr><tr></tr>" +
|
||||
"</table>";
|
||||
assert.equal(result.value, expectedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
test('table cells are written with colSpan if not equal to one', function() {
|
||||
var table = new documents.Table([
|
||||
new documents.TableRow([
|
||||
new documents.TableCell([paragraphOfText("Top left")], {colSpan: 2}),
|
||||
new documents.TableCell([paragraphOfText("Top right")])
|
||||
])
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(table).then(function(result) {
|
||||
var expectedHtml = "<table>" +
|
||||
"<tr><td colspan=\"2\"><p>Top left</p></td><td><p>Top right</p></td></tr>" +
|
||||
"</table>";
|
||||
assert.equal(result.value, expectedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
test('table cells are written with rowSpan if not equal to one', function() {
|
||||
var table = new documents.Table([
|
||||
new documents.TableRow([
|
||||
new documents.TableCell([], {rowSpan: 2})
|
||||
])
|
||||
]);
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(table).then(function(result) {
|
||||
var expectedHtml = "<table>" +
|
||||
"<tr><td rowspan=\"2\"></td></tr>" +
|
||||
"</table>";
|
||||
assert.equal(result.value, expectedHtml);
|
||||
});
|
||||
});
|
||||
|
||||
test('line break is converted to <br>', function() {
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(documents.lineBreak).then(function(result) {
|
||||
assert.equal(result.value, "<br />");
|
||||
});
|
||||
});
|
||||
|
||||
test('breaks that are not line breaks are ignored', function() {
|
||||
var converter = new DocumentConverter();
|
||||
|
||||
return converter.convertToHtml(documents.pageBreak).then(function(result) {
|
||||
assert.equal(result.value, "");
|
||||
});
|
||||
});
|
||||
|
||||
test('breaks can be mapped using style mappings', function() {
|
||||
var converter = new DocumentConverter({
|
||||
styleMap: [
|
||||
{
|
||||
from: documentMatchers.pageBreak,
|
||||
to: htmlPaths.topLevelElement("hr")
|
||||
},
|
||||
{
|
||||
from: documentMatchers.lineBreak,
|
||||
to: htmlPaths.topLevelElement("br", {class: "line-break"})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
var run = documents.run([documents.pageBreak, documents.lineBreak]);
|
||||
|
||||
return converter.convertToHtml(run).then(function(result) {
|
||||
assert.equal(result.value, '<hr /><br class="line-break" />');
|
||||
});
|
||||
});
|
||||
|
||||
test('footnote reference is converted to superscript intra-page link', function() {
|
||||
var footnoteReference = new documents.NoteReference({
|
||||
noteType: "footnote",
|
||||
noteId: "4"
|
||||
});
|
||||
var converter = new DocumentConverter({
|
||||
idPrefix: "doc-42-"
|
||||
});
|
||||
return converter.convertToHtml(footnoteReference).then(function(result) {
|
||||
assert.equal(result.value, '<sup><a href="#doc-42-footnote-4" id="doc-42-footnote-ref-4">[1]</a></sup>');
|
||||
});
|
||||
});
|
||||
|
||||
test('footnotes are included after the main body', function() {
|
||||
var footnoteReference = new documents.NoteReference({
|
||||
noteType: "footnote",
|
||||
noteId: "4"
|
||||
});
|
||||
var document = new documents.Document(
|
||||
[new documents.Paragraph([
|
||||
runOfText("Knock knock"),
|
||||
new documents.Run([footnoteReference])
|
||||
])],
|
||||
{
|
||||
notes: new documents.Notes({
|
||||
4: new documents.Note({
|
||||
noteType: "footnote",
|
||||
noteId: "4",
|
||||
body: [paragraphOfText("Who's there?")]
|
||||
})
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
var converter = new DocumentConverter({
|
||||
idPrefix: "doc-42-"
|
||||
});
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
var expectedOutput = '<p>Knock knock<sup><a href="#doc-42-footnote-4" id="doc-42-footnote-ref-4">[1]</a></sup></p>' +
|
||||
'<ol><li id="doc-42-footnote-4"><p>Who\'s there? <a href="#doc-42-footnote-ref-4">↑</a></p></li></ol>';
|
||||
assert.equal(result.value, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
test('comments are ignored by default', function() {
|
||||
var reference = documents.commentReference({commentId: "4"});
|
||||
var comment = documents.comment({
|
||||
commentId: "4",
|
||||
body: [paragraphOfText("Who's there?")]
|
||||
});
|
||||
var document = documents.document([
|
||||
documents.paragraph([
|
||||
runOfText("Knock knock"),
|
||||
documents.run([reference])
|
||||
])
|
||||
], {comments: [comment]});
|
||||
|
||||
var converter = new DocumentConverter({});
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value, '<p>Knock knock</p>');
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('comment references are linked to comment after main body', function() {
|
||||
var reference = documents.commentReference({commentId: "4"});
|
||||
var comment = documents.comment({
|
||||
commentId: "4",
|
||||
body: [paragraphOfText("Who's there?")],
|
||||
authorName: "The Piemaker",
|
||||
authorInitials: "TP"
|
||||
});
|
||||
var document = documents.document([
|
||||
documents.paragraph([
|
||||
runOfText("Knock knock"),
|
||||
documents.run([reference])
|
||||
])
|
||||
], {comments: [comment]});
|
||||
|
||||
var converter = new DocumentConverter({
|
||||
idPrefix: "doc-42-",
|
||||
styleMap: [
|
||||
{from: documentMatchers.commentReference, to: htmlPaths.element("sup")}
|
||||
]
|
||||
});
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
var expectedHtml = (
|
||||
'<p>Knock knock<sup><a href="#doc-42-comment-4" id="doc-42-comment-ref-4">[TP1]</a></sup></p>' +
|
||||
'<dl><dt id="doc-42-comment-4">Comment [TP1]</dt><dd><p>Who\'s there? <a href="#doc-42-comment-ref-4">↑</a></p></dd></dl>'
|
||||
);
|
||||
assert.equal(result.value, expectedHtml);
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('images are written with data URIs', function() {
|
||||
var imageBuffer = new Buffer("Not an image at all!");
|
||||
var image = new documents.Image({
|
||||
readImage: function(encoding) {
|
||||
return promises.resolve(imageBuffer.toString(encoding));
|
||||
},
|
||||
contentType: "image/png"
|
||||
});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(image).then(function(result) {
|
||||
assert.equal(result.value, '<img src="data:image/png;base64,' + imageBuffer.toString("base64") + '" />');
|
||||
});
|
||||
});
|
||||
|
||||
test('images have alt attribute if available', function() {
|
||||
var imageBuffer = new Buffer("Not an image at all!");
|
||||
var image = new documents.Image({
|
||||
readImage: function() {
|
||||
return promises.resolve(imageBuffer);
|
||||
},
|
||||
altText: "It's a hat"
|
||||
});
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(image)
|
||||
.then(function(result) {
|
||||
return xml.readString(result.value);
|
||||
})
|
||||
.then(function(htmlImageElement) {
|
||||
assert.equal(htmlImageElement.attributes.alt, "It's a hat");
|
||||
});
|
||||
});
|
||||
|
||||
test('can add custom handler for images', function() {
|
||||
var imageBuffer = new Buffer("Not an image at all!");
|
||||
var image = new documents.Image({
|
||||
readImage: function(encoding) {
|
||||
return promises.resolve(imageBuffer.toString(encoding));
|
||||
},
|
||||
contentType: "image/png"
|
||||
});
|
||||
var converter = new DocumentConverter({
|
||||
convertImage: function(element, messages) {
|
||||
return element.read("utf8").then(function(altText) {
|
||||
return [Html.freshElement("img", {alt: altText})];
|
||||
});
|
||||
}
|
||||
});
|
||||
return converter.convertToHtml(image).then(function(result) {
|
||||
assert.equal(result.value, '<img alt="Not an image at all!" />');
|
||||
});
|
||||
});
|
||||
|
||||
test('when custom image handler throws error then error is stored in error message', function() {
|
||||
var error = new Error("Failed to convert image");
|
||||
var image = new documents.Image({
|
||||
readImage: function(encoding) {
|
||||
return promises.resolve(new Buffer().toString(encoding));
|
||||
},
|
||||
contentType: "image/png"
|
||||
});
|
||||
var converter = new DocumentConverter({
|
||||
convertImage: function(element, messages) {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
return converter.convertToHtml(image).then(function(result) {
|
||||
assert.equal(result.value, '');
|
||||
assert.equal(result.messages.length, 1);
|
||||
var message = result.messages[0];
|
||||
assert.equal("error", message.type);
|
||||
assert.equal("Failed to convert image", message.message);
|
||||
assert.equal(error, message.error);
|
||||
});
|
||||
});
|
||||
|
||||
test('long documents do not cause stack overflow', function() {
|
||||
var paragraphs = [];
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
paragraphs.push(paragraphOfText("Hello."));
|
||||
}
|
||||
var document = new documents.Document(paragraphs);
|
||||
var converter = new DocumentConverter();
|
||||
return converter.convertToHtml(document).then(function(result) {
|
||||
assert.equal(result.value.indexOf("<p>Hello.</p>"), 0);
|
||||
});
|
||||
});
|
||||
|
||||
function paragraphOfText(text, styleId, styleName) {
|
||||
var run = runOfText(text);
|
||||
return new documents.Paragraph([run], {
|
||||
styleId: styleId,
|
||||
styleName: styleName
|
||||
});
|
||||
}
|
||||
|
||||
function runOfText(text, properties) {
|
||||
var textElement = new documents.Text(text);
|
||||
return new documents.Run([textElement], properties);
|
||||
}
|
||||
|
||||
test('when initials are not blank then comment author label is initials', function() {
|
||||
assert.equal(commentAuthorLabel({authorInitials: "TP"}), "TP");
|
||||
});
|
||||
|
||||
test('when initials are blank then comment author label is blank', function() {
|
||||
assert.equal(commentAuthorLabel({authorInitials: ""}), "");
|
||||
assert.equal(commentAuthorLabel({authorInitials: undefined}), "");
|
||||
assert.equal(commentAuthorLabel({authorInitials: null}), "");
|
||||
});
|
||||
+2100
File diff suppressed because it is too large.
Load diff
+53
@@ -0,0 +1,53 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var createCommentsReader = require("../../lib/docx/comments-reader").createCommentsReader;
|
||||
var createBodyReader = require("../../lib/docx/body-reader").createBodyReader;
|
||||
var stylesReader = require("../../lib/docx/styles-reader");
|
||||
var documents = require("../../lib/documents");
|
||||
var xml = require("../../lib/xml");
|
||||
var test = require("../test")(module);
|
||||
|
||||
|
||||
function readComment(element) {
|
||||
var bodyReader = createBodyReader({styles: stylesReader.defaultStyles});
|
||||
var commentsReader = createCommentsReader(bodyReader);
|
||||
var comments = commentsReader(element);
|
||||
assert.equal(comments.value.length, 1);
|
||||
return comments.value[0];
|
||||
}
|
||||
|
||||
test('ID and body of comment are read', function() {
|
||||
var body = [xml.element("w:p")];
|
||||
var comment = readComment(xml.element("w:comments", {}, [
|
||||
xml.element("w:comment", {"w:id": "1"}, body)
|
||||
]));
|
||||
assert.deepEqual(comment.body, [new documents.Paragraph([])]);
|
||||
assert.deepEqual(comment.commentId, "1");
|
||||
});
|
||||
|
||||
|
||||
test('when optional attributes of comment are missing then they are read as null', function() {
|
||||
var comment = readComment(xml.element("w:comments", {}, [
|
||||
xml.element("w:comment", {"w:id": "1"})
|
||||
]));
|
||||
assert.strictEqual(comment.authorName, null);
|
||||
assert.strictEqual(comment.authorInitials, null);
|
||||
});
|
||||
|
||||
|
||||
test('when optional attributes of comment are blank then they are read as null', function() {
|
||||
var comment = readComment(xml.element("w:comments", {}, [
|
||||
xml.element("w:comment", {"w:id": "1", "w:author": " ", "w:initials": " "})
|
||||
]));
|
||||
assert.strictEqual(comment.authorName, null);
|
||||
assert.strictEqual(comment.authorInitials, null);
|
||||
});
|
||||
|
||||
|
||||
test('when optional attributes of comment are not blank then they are read', function() {
|
||||
var comment = readComment(xml.element("w:comments", {}, [
|
||||
xml.element("w:comment", {"w:id": "1", "w:author": "The Piemaker", "w:initials": "TP"})
|
||||
]));
|
||||
assert.strictEqual(comment.authorName, "The Piemaker");
|
||||
assert.strictEqual(comment.authorInitials, "TP");
|
||||
});
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var readContentTypesFromXml = require("../../lib/docx/content-types-reader").readContentTypesFromXml;
|
||||
var XmlElement = require("../../lib/xml").Element;
|
||||
var test = require("../test")(module);
|
||||
|
||||
|
||||
test('reads default-per-extension from XML', function() {
|
||||
var contentTypes = readContentTypesFromXml(
|
||||
new XmlElement("content-types:Types", {}, [
|
||||
new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"})
|
||||
])
|
||||
);
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png");
|
||||
});
|
||||
|
||||
test('reads overrides in preference to defaults', function() {
|
||||
var contentTypes = readContentTypesFromXml(
|
||||
new XmlElement("content-types:Types", {}, [
|
||||
new XmlElement("content-types:Default", {Extension: "png", ContentType: "image/png"}),
|
||||
new XmlElement("content-types:Override", {PartName: "/word/media/hat.png", ContentType: "image/hat"})
|
||||
])
|
||||
);
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/hat");
|
||||
});
|
||||
|
||||
test('fallback content types have common image types', function() {
|
||||
var contentTypes = readContentTypesFromXml(
|
||||
new XmlElement("content-types:Types", {}, [])
|
||||
);
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.png"), "image/png");
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.gif"), "image/gif");
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.jpg"), "image/jpeg");
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.jpeg"), "image/jpeg");
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.bmp"), "image/bmp");
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.tif"), "image/tiff");
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.tiff"), "image/tiff");
|
||||
});
|
||||
|
||||
test('fallback content types are case insensitive on extension', function() {
|
||||
var contentTypes = readContentTypesFromXml(
|
||||
new XmlElement("content-types:Types", {}, [])
|
||||
);
|
||||
assert.equal(contentTypes.findContentType("word/media/hat.PnG"), "image/png");
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
var hamjest = require("hamjest");
|
||||
var _ = require("underscore");
|
||||
|
||||
var documents = require("../../lib/documents");
|
||||
|
||||
|
||||
exports.isEmptyRun = isRun({children: []});
|
||||
exports.isRun = isRun;
|
||||
exports.isText = isText;
|
||||
exports.isCheckbox = isCheckbox;
|
||||
exports.isHyperlink = isHyperlink;
|
||||
exports.isTable = isTable;
|
||||
exports.isRow = isRow;
|
||||
|
||||
|
||||
function isRun(properties) {
|
||||
return isDocumentElement(documents.types.run, properties);
|
||||
}
|
||||
|
||||
function isText(text) {
|
||||
return isDocumentElement(documents.types.text, {value: text});
|
||||
}
|
||||
|
||||
function isCheckbox(properties) {
|
||||
return isDocumentElement(documents.types.checkbox, properties);
|
||||
}
|
||||
|
||||
function isHyperlink(properties) {
|
||||
return isDocumentElement(documents.types.hyperlink, properties);
|
||||
}
|
||||
|
||||
function isTable(options) {
|
||||
return isDocumentElement(documents.types.table, options);
|
||||
}
|
||||
|
||||
function isRow(options) {
|
||||
return isDocumentElement(documents.types.tableRow, options);
|
||||
}
|
||||
|
||||
function isDocumentElement(type, properties) {
|
||||
return hamjest.hasProperties(_.extend({type: hamjest.equalTo(type)}, properties));
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var documents = require("../../lib/documents");
|
||||
var DocumentXmlReader = require("../../lib/docx/document-xml-reader").DocumentXmlReader;
|
||||
var xml = require("../../lib/xml");
|
||||
var test = require("../test")(module);
|
||||
var createBodyReaderForTests = require("./testing").createBodyReaderForTests;
|
||||
|
||||
test("when body element is present then body is read", function() {
|
||||
var bodyReader = createBodyReaderForTests({});
|
||||
var documentXmlReader = new DocumentXmlReader({
|
||||
bodyReader: bodyReader
|
||||
});
|
||||
var textXml = xml.element("w:t", {}, [xml.text("Hello!")]);
|
||||
var runXml = xml.element("w:r", {}, [textXml]);
|
||||
var paragraphXml = xml.element("w:p", {}, [runXml]);
|
||||
var bodyXml = xml.element("w:body", {}, [paragraphXml]);
|
||||
var documentXml = xml.element("w:document", {}, [bodyXml]);
|
||||
|
||||
var result = documentXmlReader.convertXmlToDocument(documentXml);
|
||||
|
||||
assert.deepEqual(result.messages, []);
|
||||
assert.deepEqual(result.value, documents.document(
|
||||
[documents.paragraph([documents.run([documents.text("Hello!")])])],
|
||||
{}
|
||||
));
|
||||
});
|
||||
|
||||
test("when body element is not present then error is thrown", function() {
|
||||
var bodyReader = createBodyReaderForTests({});
|
||||
var documentXmlReader = new DocumentXmlReader({
|
||||
bodyReader: bodyReader
|
||||
});
|
||||
var paragraphXml = xml.element("w:p", {}, []);
|
||||
var bodyXml = xml.element("w:body2", {}, [paragraphXml]);
|
||||
var documentXml = xml.element("w:document", {}, [bodyXml]);
|
||||
|
||||
assert.throws(function() {
|
||||
documentXmlReader.convertXmlToDocument(documentXml);
|
||||
}, /Could not find the body element: are you sure this is a docx file?/);
|
||||
});
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var docxReader = require("../../lib/docx/docx-reader");
|
||||
var documents = require("../../lib/documents");
|
||||
var xml = require("../../lib/xml");
|
||||
|
||||
var testing = require("../testing");
|
||||
var test = require("../test")(module);
|
||||
var testData = testing.testData;
|
||||
var createFakeDocxFile = testing.createFakeDocxFile;
|
||||
|
||||
|
||||
test("can read document with single paragraph with single run of text", function() {
|
||||
var expectedDocument = documents.Document([
|
||||
documents.Paragraph([
|
||||
documents.Run([
|
||||
documents.Text("Hello.")
|
||||
])
|
||||
])
|
||||
]);
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document.xml": testData("simple/word/document.xml")
|
||||
});
|
||||
return docxReader.read(docxFile).then(function(result) {
|
||||
assert.deepEqual(expectedDocument, result.value);
|
||||
});
|
||||
});
|
||||
|
||||
test("hyperlink hrefs are read from relationships file", function() {
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document.xml": testData("hyperlinks/word/document.xml"),
|
||||
"word/_rels/document.xml.rels": testData("hyperlinks/word/_rels/document.xml.rels")
|
||||
});
|
||||
return docxReader.read(docxFile).then(function(result) {
|
||||
var paragraph = result.value.children[0];
|
||||
assert.equal(1, paragraph.children.length);
|
||||
var hyperlink = paragraph.children[0];
|
||||
assert.equal(hyperlink.href, "http://www.example.com");
|
||||
assert.equal(hyperlink.children.length, 1);
|
||||
});
|
||||
});
|
||||
|
||||
var relationshipNamespaces = {
|
||||
"r": "http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
};
|
||||
|
||||
test("main document is found using _rels/.rels", function() {
|
||||
var relationships = xml.element("r:Relationships", {}, [
|
||||
xml.element("r:Relationship", {
|
||||
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
||||
"Target": "/word/document2.xml"
|
||||
})
|
||||
]);
|
||||
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document2.xml": testData("simple/word/document.xml"),
|
||||
"_rels/.rels": xml.writeString(relationships, relationshipNamespaces)
|
||||
});
|
||||
var expectedDocument = documents.Document([
|
||||
documents.Paragraph([
|
||||
documents.Run([
|
||||
documents.Text("Hello.")
|
||||
])
|
||||
])
|
||||
]);
|
||||
return docxReader.read(docxFile).then(function(result) {
|
||||
assert.deepEqual(expectedDocument, result.value);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test("error is thrown when main document part does not exist", function() {
|
||||
var relationships = xml.element("r:Relationships", {}, [
|
||||
xml.element("r:Relationship", {
|
||||
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
||||
"Target": "/word/document2.xml"
|
||||
})
|
||||
]);
|
||||
|
||||
var docxFile = createFakeDocxFile({
|
||||
"_rels/.rels": xml.writeString(relationships, relationshipNamespaces)
|
||||
});
|
||||
return docxReader.read(docxFile).then(function(result) {
|
||||
assert.ok(false, "Expected error");
|
||||
}, function(error) {
|
||||
assert.equal(error.message, "Could not find main document part. Are you sure this is a valid .docx file?");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test("part paths", {
|
||||
"main document part is found using package relationships": function() {
|
||||
var relationships = xml.element("r:Relationships", {}, [
|
||||
xml.element("r:Relationship", {
|
||||
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
||||
"Target": "/word/document2.xml"
|
||||
})
|
||||
]);
|
||||
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document2.xml": " ",
|
||||
"_rels/.rels": xml.writeString(relationships, relationshipNamespaces)
|
||||
});
|
||||
return docxReader._findPartPaths(docxFile).then(function(partPaths) {
|
||||
assert.equal(partPaths.mainDocument, "word/document2.xml");
|
||||
});
|
||||
},
|
||||
|
||||
"word/document.xml is used as fallback location for main document part": function() {
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document.xml": " "
|
||||
});
|
||||
return docxReader._findPartPaths(docxFile).then(function(partPaths) {
|
||||
assert.equal(partPaths.mainDocument, "word/document.xml");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
name: "comments",
|
||||
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
|
||||
},
|
||||
{
|
||||
name: "endnotes",
|
||||
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes"
|
||||
},
|
||||
{
|
||||
name: "footnotes",
|
||||
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes"
|
||||
},
|
||||
{
|
||||
name: "numbering",
|
||||
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering"
|
||||
},
|
||||
{
|
||||
name: "styles",
|
||||
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
|
||||
}
|
||||
].forEach(function(options) {
|
||||
test(options.name + " part is found using main document relationships", function() {
|
||||
var docxFile = createFakeDocxFile({
|
||||
"_rels/.rels": createPackageRelationships("word/document.xml"),
|
||||
"word/document.xml": " ",
|
||||
"word/_rels/document.xml.rels": xml.writeString(xml.element("r:Relationships", {}, [
|
||||
xml.element("r:Relationship", {
|
||||
"Type": options.type,
|
||||
"Target": "target-path.xml"
|
||||
})
|
||||
]), relationshipNamespaces),
|
||||
"word/target-path.xml": " "
|
||||
});
|
||||
return docxReader._findPartPaths(docxFile).then(function(partPaths) {
|
||||
assert.equal(partPaths[options.name], "word/target-path.xml");
|
||||
});
|
||||
});
|
||||
|
||||
test("word/" + options.name + ".xml is used as fallback location for " + options.name + " part", function() {
|
||||
var zipContents = {
|
||||
"_rels/.rels": createPackageRelationships("word/document.xml"),
|
||||
"word/document.xml": " "
|
||||
};
|
||||
zipContents["word/" + options.name + ".xml"] = " ";
|
||||
var docxFile = createFakeDocxFile(zipContents);
|
||||
return docxReader._findPartPaths(docxFile).then(function(partPaths) {
|
||||
assert.equal(partPaths[options.name], "word/" + options.name + ".xml");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function createPackageRelationships(mainDocumentPath) {
|
||||
return xml.writeString(xml.element("r:Relationships", {}, [
|
||||
xml.element("r:Relationship", {
|
||||
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
||||
"Target": mainDocumentPath
|
||||
})
|
||||
]), relationshipNamespaces);
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
var path = require("path");
|
||||
var assert = require("assert");
|
||||
|
||||
var Files = require("../../lib/docx/files").Files;
|
||||
var uriToPath = require("../../lib/docx/files").uriToPath;
|
||||
var fs = require("../../lib/fs");
|
||||
|
||||
var testing = require("../testing");
|
||||
var test = require("../test")(module);
|
||||
|
||||
|
||||
test("Files", {
|
||||
"when external file access is disabled then reading file raises error": function() {
|
||||
var files = new Files({externalFileAccess: false});
|
||||
return assertError(files.read("/tmp/image.png", "base64"), function(err) {
|
||||
assert.strictEqual(err.message, "could not read external image '/tmp/image.png', external file access is disabled");
|
||||
});
|
||||
},
|
||||
|
||||
"can open files with file URI": function() {
|
||||
var filePath = path.resolve(testing.testPath("tiny-picture.png"));
|
||||
var files = new Files({externalFileAccess: true});
|
||||
return files.read("file:///" + filePath.replace(/^\//, ""), "base64").then(function(contents) {
|
||||
return fs.readFile(filePath, "base64").then(function(expectedContents) {
|
||||
assert.deepEqual(contents, expectedContents);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
"can open files with relative URI": function() {
|
||||
var filePath = path.resolve(testing.testPath("tiny-picture.png"));
|
||||
var files = new Files({
|
||||
externalFileAccess: true,
|
||||
relativeToFile: testing.testPath("./document.docx")
|
||||
});
|
||||
return files.read("tiny-picture.png", "base64").then(function(contents) {
|
||||
return fs.readFile(filePath, "base64").then(function(expectedContents) {
|
||||
assert.deepEqual(contents, expectedContents);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
"given base is not set when opening relative uri then error is raised": function() {
|
||||
var files = new Files({externalFileAccess: true});
|
||||
return assertError(files.read("not-a-real-file.png", "base64"), function(err) {
|
||||
assert.equal(err.message, "could not find external image 'not-a-real-file.png', path of input document is unknown");
|
||||
});
|
||||
},
|
||||
|
||||
"error if relative uri cannot be opened": function() {
|
||||
var files = new Files({
|
||||
externalFileAccess: true,
|
||||
relativeToFile: "/tmp/document.docx"
|
||||
});
|
||||
return assertError(files.read("not-a-real-file.png", "base64"), function(err) {
|
||||
assertRegex(err.message, /could not open external image: 'not-a-real-file.png' \(document directory: '\/tmp'\)\nENOENT.*\/tmp\/not-a-real-file.png.*/);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function assertError(promise, func) {
|
||||
return promise.then(function() {
|
||||
assert(false, "Expected error");
|
||||
}, func);
|
||||
}
|
||||
|
||||
function assertRegex(actual, expected) {
|
||||
assert.ok(expected.test(actual), "Expected regex: " + expected + "\nbut was: " + actual);
|
||||
}
|
||||
|
||||
|
||||
test("uriToPath", {
|
||||
"leading slash is retained on non-Windows file URIs": function() {
|
||||
assert.equal(uriToPath("file:///a/b/c", "linux"), "/a/b/c");
|
||||
assert.equal(uriToPath("file:///a/b/c", "win32"), "/a/b/c");
|
||||
},
|
||||
|
||||
"URI is unquoted": function() {
|
||||
assert.equal(uriToPath("file:///a%20b"), "/a b");
|
||||
},
|
||||
|
||||
"when host is set to localhost then path can be found": function() {
|
||||
assert.equal(uriToPath("file://localhost/a/b/c"), "/a/b/c");
|
||||
},
|
||||
|
||||
"when host is set but not localhost then path cannot be found": function() {
|
||||
assert.throws(function() {
|
||||
uriToPath("file://example/a/b/c");
|
||||
}, /Could not convert URI to path: file:\/\/example\/a\/b\/c/);
|
||||
},
|
||||
|
||||
"leading slash is not dropped on Windows file URIs when platform is not Windows": function() {
|
||||
assert.equal(uriToPath("file:///c:/a", "linux"), "/c:/a");
|
||||
},
|
||||
|
||||
"leading slash is dropped on Windows file URIs when platform is Windows": function() {
|
||||
assert.equal(uriToPath("file:///c:/a", "win32"), "c:/a");
|
||||
assert.equal(uriToPath("file:///C:/a", "win32"), "C:/a");
|
||||
},
|
||||
|
||||
"relative URI is unquoted": function() {
|
||||
assert.equal(uriToPath("a%20b/c"), "a b/c");
|
||||
}
|
||||
});
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var createFootnotesReader = require("../../lib/docx/notes-reader").createFootnotesReader;
|
||||
var createBodyReader = require("../../lib/docx/body-reader").createBodyReader;
|
||||
var stylesReader = require("../../lib/docx/styles-reader");
|
||||
var documents = require("../../lib/documents");
|
||||
var XmlElement = require("../../lib/xml").Element;
|
||||
var test = require("../test")(module);
|
||||
|
||||
|
||||
test('ID and body of footnote are read', function() {
|
||||
var bodyReader = new createBodyReader({styles: stylesReader.defaultStyles});
|
||||
var footnoteBody = [new XmlElement("w:p", {}, [])];
|
||||
var footnotes = createFootnotesReader(bodyReader)(
|
||||
new XmlElement("w:footnotes", {}, [
|
||||
new XmlElement("w:footnote", {"w:id": "1"}, footnoteBody)
|
||||
])
|
||||
);
|
||||
assert.equal(footnotes.value.length, 1);
|
||||
assert.deepEqual(footnotes.value[0].body, [new documents.Paragraph([])]);
|
||||
assert.deepEqual(footnotes.value[0].noteId, "1");
|
||||
});
|
||||
|
||||
footnoteTypeIsIgnored('continuationSeparator');
|
||||
footnoteTypeIsIgnored('separator');
|
||||
|
||||
function footnoteTypeIsIgnored(type) {
|
||||
test('footnotes of type ' + type + ' are ignored', function() {
|
||||
var footnotes = createFootnotesReader()(
|
||||
new XmlElement("w:footnotes", {}, [
|
||||
new XmlElement("w:footnote", {"w:id": "1", "w:type": type}, [])
|
||||
])
|
||||
);
|
||||
assert.equal(footnotes.value.length, 0);
|
||||
});
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
var assert = require("assert");
|
||||
var duck = require("duck");
|
||||
|
||||
var readNumberingXml = require("../../lib/docx/numbering-xml").readNumberingXml;
|
||||
var stylesReader = require("../../lib/docx/styles-reader");
|
||||
var XmlElement = require("../../lib/xml").Element;
|
||||
var test = require("../test")(module);
|
||||
|
||||
|
||||
test('w:num element inherits levels from w:abstractNum', function() {
|
||||
var numbering = readNumberingXml(
|
||||
new XmlElement("w:numbering", {}, [
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
|
||||
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
|
||||
new XmlElement("w:numFmt", {"w:val": "bullet"})
|
||||
]),
|
||||
new XmlElement("w:lvl", {"w:ilvl": "1"}, [
|
||||
new XmlElement("w:numFmt", {"w:val": "decimal"})
|
||||
])
|
||||
]),
|
||||
new XmlElement("w:num", {"w:numId": "47"}, [
|
||||
new XmlElement("w:abstractNumId", {"w:val": "42"})
|
||||
])
|
||||
]),
|
||||
{styles: stylesReader.defaultStyles}
|
||||
);
|
||||
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
|
||||
isOrdered: false
|
||||
}));
|
||||
duck.assertThat(numbering.findLevel("47", "1"), duck.hasProperties({
|
||||
isOrdered: true
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
test('w:num element referencing non-existent w:abstractNumId is ignored', function() {
|
||||
var numbering = readNumberingXml(
|
||||
new XmlElement("w:numbering", {}, [
|
||||
new XmlElement("w:num", {"w:numId": "47"}, [
|
||||
new XmlElement("w:abstractNumId", {"w:val": "42"})
|
||||
])
|
||||
]),
|
||||
{styles: stylesReader.defaultStyles}
|
||||
);
|
||||
duck.assertThat(numbering.findLevel("47", "0"), duck.equalTo(null));
|
||||
});
|
||||
|
||||
test('given no other levels with index of 0 when level is missing w:ilvl then level index is 0', function() {
|
||||
var numbering = readNumberingXml(
|
||||
new XmlElement("w:numbering", {}, [
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
|
||||
new XmlElement("w:lvl", {}, [
|
||||
new XmlElement("w:numFmt", {"w:val": "decimal"})
|
||||
])
|
||||
]),
|
||||
new XmlElement("w:num", {"w:numId": "47"}, [
|
||||
new XmlElement("w:abstractNumId", {"w:val": "42"})
|
||||
])
|
||||
]),
|
||||
{styles: stylesReader.defaultStyles}
|
||||
);
|
||||
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
|
||||
isOrdered: true
|
||||
}));
|
||||
});
|
||||
|
||||
test('given previous other level with index of 0 when level is missing w:ilvl then level is ignored', function() {
|
||||
var numbering = readNumberingXml(
|
||||
new XmlElement("w:numbering", {}, [
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
|
||||
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
|
||||
new XmlElement("w:numFmt", {"w:val": "bullet"})
|
||||
]),
|
||||
new XmlElement("w:lvl", {}, [
|
||||
new XmlElement("w:numFmt", {"w:val": "decimal"})
|
||||
])
|
||||
]),
|
||||
new XmlElement("w:num", {"w:numId": "47"}, [
|
||||
new XmlElement("w:abstractNumId", {"w:val": "42"})
|
||||
])
|
||||
]),
|
||||
{styles: stylesReader.defaultStyles}
|
||||
);
|
||||
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
|
||||
isOrdered: false
|
||||
}));
|
||||
});
|
||||
|
||||
test('given subsequent other level with index of 0 when level is missing w:ilvl then level is ignored', function() {
|
||||
var numbering = readNumberingXml(
|
||||
new XmlElement("w:numbering", {}, [
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
|
||||
new XmlElement("w:lvl", {}, [
|
||||
new XmlElement("w:numFmt", {"w:val": "decimal"})
|
||||
]),
|
||||
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
|
||||
new XmlElement("w:numFmt", {"w:val": "bullet"})
|
||||
])
|
||||
]),
|
||||
new XmlElement("w:num", {"w:numId": "47"}, [
|
||||
new XmlElement("w:abstractNumId", {"w:val": "42"})
|
||||
])
|
||||
]),
|
||||
{styles: stylesReader.defaultStyles}
|
||||
);
|
||||
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
|
||||
isOrdered: false
|
||||
}));
|
||||
});
|
||||
|
||||
test('when level is missing w:numFmt then level is ordered', function() {
|
||||
var numbering = readNumberingXml(
|
||||
new XmlElement("w:numbering", {}, [
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
|
||||
new XmlElement("w:lvl", {"w:ilvl": "0"})
|
||||
]),
|
||||
new XmlElement("w:num", {"w:numId": "47"}, [
|
||||
new XmlElement("w:abstractNumId", {"w:val": "42"})
|
||||
])
|
||||
]),
|
||||
{styles: stylesReader.defaultStyles}
|
||||
);
|
||||
duck.assertThat(numbering.findLevel("47", "0"), duck.hasProperties({
|
||||
isOrdered: true
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
test('when w:abstractNum has w:numStyleLink then style is used to find w:num', function() {
|
||||
var numbering = readNumberingXml(
|
||||
new XmlElement("w:numbering", {}, [
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "100"}, [
|
||||
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
|
||||
new XmlElement("w:numFmt", {"w:val": "decimal"})
|
||||
])
|
||||
]),
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "101"}, [
|
||||
new XmlElement("w:numStyleLink", {"w:val": "List1"})
|
||||
]),
|
||||
new XmlElement("w:num", {"w:numId": "200"}, [
|
||||
new XmlElement("w:abstractNumId", {"w:val": "100"})
|
||||
]),
|
||||
new XmlElement("w:num", {"w:numId": "201"}, [
|
||||
new XmlElement("w:abstractNumId", {"w:val": "101"})
|
||||
])
|
||||
]),
|
||||
{styles: new stylesReader.Styles({}, {}, {}, {"List1": {numId: "200"}})}
|
||||
);
|
||||
duck.assertThat(numbering.findLevel("201", "0"), duck.hasProperties({
|
||||
isOrdered: true
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
test('when w:abstractNum has self-recursive w:numStyleLink then level is not found', function() {
|
||||
var numbering = readNumberingXml(
|
||||
new XmlElement("w:numbering", {}, [
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "100"}, [
|
||||
new XmlElement("w:numStyleLink", {"w:val": "List1"})
|
||||
]),
|
||||
new XmlElement("w:num", {"w:numId": "200"}, [
|
||||
new XmlElement("w:abstractNumId", {"w:val": "100"})
|
||||
])
|
||||
]),
|
||||
{styles: new stylesReader.Styles({}, {}, {}, {"List1": {numId: "200"}})}
|
||||
);
|
||||
duck.assertThat(numbering.findLevel("200", "0"), duck.equalTo(null));
|
||||
});
|
||||
|
||||
|
||||
// See: 17.9.23 pStyle (Paragraph Style's Associated Numbering Level) in ECMA-376, 4th Edition
|
||||
test('numbering level can be found by paragraph style ID', function() {
|
||||
var numbering = readNumberingXml(
|
||||
new XmlElement("w:numbering", {}, [
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "42"}, [
|
||||
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
|
||||
new XmlElement("w:numFmt", {"w:val": "bullet"})
|
||||
])
|
||||
]),
|
||||
new XmlElement("w:abstractNum", {"w:abstractNumId": "43"}, [
|
||||
new XmlElement("w:lvl", {"w:ilvl": "0"}, [
|
||||
new XmlElement("w:pStyle", {"w:val": "List"}),
|
||||
new XmlElement("w:numFmt", {"w:val": "decimal"})
|
||||
])
|
||||
])
|
||||
]),
|
||||
{styles: stylesReader.defaultStyles}
|
||||
);
|
||||
duck.assertThat(numbering.findLevelByParagraphStyleId("List"), duck.hasProperties({
|
||||
isOrdered: true
|
||||
}));
|
||||
duck.assertThat(numbering.findLevelByParagraphStyleId("Paragraph"), duck.equalTo(null));
|
||||
});
|
||||
|
||||
test('when styles is missing then error is thrown', function() {
|
||||
assert.throws(function() {
|
||||
readNumberingXml(new XmlElement("w:numbering", {}, []));
|
||||
}, /styles is missing/);
|
||||
});
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var xml = require("../../lib/xml");
|
||||
var officeXmlReader = require("../../lib/docx/office-xml-reader");
|
||||
var test = require("../test")(module);
|
||||
|
||||
|
||||
test("mc:AlternateContent", {
|
||||
"when mc:Fallback is present then mc:Fallback is read": function() {
|
||||
var xmlString =
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
||||
'<numbering xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">' +
|
||||
'<mc:AlternateContent>' +
|
||||
'<mc:Choice Requires="w14">' +
|
||||
'<choice/>' +
|
||||
'</mc:Choice>' +
|
||||
'<mc:Fallback>' +
|
||||
'<fallback/>' +
|
||||
'</mc:Fallback>' +
|
||||
'</mc:AlternateContent>' +
|
||||
'</numbering>';
|
||||
return officeXmlReader.read(xmlString).then(function(element) {
|
||||
assert.deepEqual(element.children, [xml.element("fallback")]);
|
||||
});
|
||||
},
|
||||
|
||||
"when mc:Fallback is not present then element is ignored": function() {
|
||||
var xmlString =
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
||||
'<numbering xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">' +
|
||||
'<mc:AlternateContent>' +
|
||||
'<mc:Choice Requires="w14">' +
|
||||
'<choice/>' +
|
||||
'</mc:Choice>' +
|
||||
'</mc:AlternateContent>' +
|
||||
'</numbering>';
|
||||
return officeXmlReader.read(xmlString).then(function(element) {
|
||||
assert.deepEqual(element.children, []);
|
||||
});
|
||||
}
|
||||
});
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var readRelationships = require("../../lib/docx/relationships-reader").readRelationships;
|
||||
var xml = require("../../lib/xml");
|
||||
var test = require("../test")(module);
|
||||
|
||||
|
||||
test("relationships can be found by ID", function() {
|
||||
var relationships = readRelationships(relationshipsElement([
|
||||
relationshipElement({
|
||||
"Id": "rId1",
|
||||
"Target": "http://example.com/",
|
||||
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
||||
}),
|
||||
relationshipElement({
|
||||
"Id": "rId2",
|
||||
"Target": "http://example.net/",
|
||||
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
||||
})
|
||||
]));
|
||||
assert.equal(relationships.findTargetByRelationshipId("rId1"), "http://example.com/");
|
||||
});
|
||||
|
||||
|
||||
test("relationships can be found by type", function() {
|
||||
var relationships = readRelationships(relationshipsElement([
|
||||
relationshipElement({
|
||||
"Id": "rId2",
|
||||
"Target": "docProps/core.xml",
|
||||
"Type": "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
|
||||
}),
|
||||
relationshipElement({
|
||||
"Id": "rId1",
|
||||
"Target": "word/document.xml",
|
||||
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
|
||||
}),
|
||||
relationshipElement({
|
||||
"Id": "rId3",
|
||||
"Target": "word/document2.xml",
|
||||
"Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
|
||||
})
|
||||
]));
|
||||
assert.deepEqual(
|
||||
relationships.findTargetsByType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"),
|
||||
["word/document.xml", "word/document2.xml"]
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
test("when there are no relationships of requested type then empty array is returned", function() {
|
||||
var relationships = readRelationships(relationshipsElement([]));
|
||||
assert.deepEqual(
|
||||
relationships.findTargetsByType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"),
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
function relationshipsElement(children) {
|
||||
return xml.element("relationships:Relationships", {}, children);
|
||||
}
|
||||
|
||||
function relationshipElement(attributes) {
|
||||
return xml.element("relationships:Relationship", attributes, []);
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var JSZip = require("jszip");
|
||||
|
||||
var zipfile = require("../../lib/zipfile");
|
||||
var styleMap = require("../../lib/docx/style-map");
|
||||
var test = require("../test")(module);
|
||||
|
||||
test('reading embedded style map on document without embedded style map returns null', function() {
|
||||
return normalDocx().then(function(zip) {
|
||||
return styleMap.readStyleMap(zip).then(function(contents) {
|
||||
assert.equal(contents, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('embedded style map can be read after being written', function() {
|
||||
return normalDocx().then(function(zip) {
|
||||
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
|
||||
return styleMap.readStyleMap(zip).then(function(contents) {
|
||||
assert.equal(contents, "p => h1");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('embedded style map is written to separate file', function() {
|
||||
return normalDocx().then(function(zip) {
|
||||
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
|
||||
return zip.read("mammoth/style-map", "utf8").then(function(contents) {
|
||||
assert.equal(contents, "p => h1");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('embedded style map is referenced in relationships', function() {
|
||||
return normalDocx().then(function(zip) {
|
||||
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
|
||||
return zip.read("word/_rels/document.xml.rels", "utf8").then(function(contents) {
|
||||
assert.equal(contents, expectedRelationshipsXml);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('re-embedding style map replaces original', function() {
|
||||
return normalDocx().then(function(zip) {
|
||||
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
|
||||
return styleMap.writeStyleMap(zip, "p => h2");
|
||||
}).then(function() {
|
||||
return zip.read("word/_rels/document.xml.rels", "utf8").then(function(contents) {
|
||||
assert.equal(contents, expectedRelationshipsXml);
|
||||
});
|
||||
}).then(function() {
|
||||
return styleMap.readStyleMap(zip).then(function(contents) {
|
||||
assert.equal(contents, "p => h2");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('embedded style map has override content type in [Content_Types].xml', function() {
|
||||
return normalDocx().then(function(zip) {
|
||||
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
|
||||
return zip.read("[Content_Types].xml", "utf8").then(function(contents) {
|
||||
assert.equal(contents, expectedContentTypesXml);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('replacing style map keeps content type', function() {
|
||||
return normalDocx().then(function(zip) {
|
||||
return styleMap.writeStyleMap(zip, "p => h1").then(function() {
|
||||
return styleMap.writeStyleMap(zip, "p => h2");
|
||||
}).then(function() {
|
||||
return zip.read("[Content_Types].xml", "utf8").then(function(contents) {
|
||||
assert.equal(contents, expectedContentTypesXml);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var expectedRelationshipsXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
|
||||
'<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>' +
|
||||
'<Relationship Id="rMammothStyleMap" Type="http://schemas.zwobble.org/mammoth/style-map" Target="/mammoth/style-map"/>' +
|
||||
'</Relationships>';
|
||||
|
||||
var expectedContentTypesXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
|
||||
'<Default Extension="png" ContentType="image/png"/>' +
|
||||
'<Override PartName="/mammoth/style-map" ContentType="text/prs.mammoth.style-map"/>' +
|
||||
'</Types>';
|
||||
|
||||
function normalDocx() {
|
||||
var zip = new JSZip();
|
||||
var originalRelationshipsXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
|
||||
'<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>' +
|
||||
'</Relationships>';
|
||||
var originalContentTypesXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
|
||||
'<Default Extension="png" ContentType="image/png"/>' +
|
||||
'</Types>';
|
||||
zip.file("word/_rels/document.xml.rels", originalRelationshipsXml);
|
||||
zip.file("[Content_Types].xml", originalContentTypesXml);
|
||||
return zip.generateAsync({type: "arraybuffer"}).then(function(buffer) {
|
||||
return zipfile.openArrayBuffer(buffer);
|
||||
});
|
||||
}
|
||||
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var readStylesXml = require("../../lib/docx/styles-reader").readStylesXml;
|
||||
var XmlElement = require("../../lib/xml").Element;
|
||||
var test = require("../test")(module);
|
||||
|
||||
|
||||
test('paragraph style is null if no style with that ID exists', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [])
|
||||
);
|
||||
assert.equal(styles.findParagraphStyleById("Heading1"), null);
|
||||
});
|
||||
|
||||
test('paragraph style can be found by ID', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
paragraphStyleElement("Heading1", "Heading 1")
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findParagraphStyleById("Heading1").styleId, "Heading1");
|
||||
});
|
||||
|
||||
test('table style can be found by ID', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
tableStyleElement("TableNormal", "Normal Table")
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findTableStyleById("TableNormal").styleId, "TableNormal");
|
||||
});
|
||||
|
||||
test('character style can be found by ID', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
characterStyleElement("Heading1Char", "Heading 1 Char")
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findCharacterStyleById("Heading1Char").styleId, "Heading1Char");
|
||||
});
|
||||
|
||||
test('paragraph and character styles are distinct', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
paragraphStyleElement("Heading1", "Heading 1"),
|
||||
characterStyleElement("Heading1Char", "Heading 1 Char")
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findCharacterStyleById("Heading1"), null);
|
||||
assert.equal(styles.findParagraphStyleById("Heading1Char"), null);
|
||||
});
|
||||
|
||||
test('character and table styles are distinct', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
tableStyleElement("Heading1", "Heading 1")
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findCharacterStyleById("Heading1"), null);
|
||||
});
|
||||
|
||||
test('styles include names', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
paragraphStyleElement("Heading1", "Heading 1")
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findParagraphStyleById("Heading1").name, "Heading 1");
|
||||
});
|
||||
|
||||
test('style name is null if w:name element does not exist', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
styleWithoutWNameElement("paragraph", "Heading1"),
|
||||
styleWithoutWNameElement("character", "Heading1Char")
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findParagraphStyleById("Heading1").name, null);
|
||||
assert.equal(styles.findCharacterStyleById("Heading1Char").name, null);
|
||||
});
|
||||
|
||||
test('numbering style is null if no style with that ID exists', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [])
|
||||
);
|
||||
assert.equal(styles.findNumberingStyleById("List1"), null);
|
||||
});
|
||||
|
||||
test('numbering style has null numId if style has no paragraph properties', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
new XmlElement("w:style", {"w:type": "numbering", "w:styleId": "List1"})
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findNumberingStyleById("List1").numId, null);
|
||||
});
|
||||
|
||||
test('numbering style has numId read from paragraph properties', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
new XmlElement("w:style", {"w:type": "numbering", "w:styleId": "List1"}, [
|
||||
new XmlElement("w:pPr", {}, [
|
||||
new XmlElement("w:numPr", {}, [
|
||||
new XmlElement("w:numId", {"w:val": "42"})
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findNumberingStyleById("List1").numId, "42");
|
||||
});
|
||||
|
||||
test('when multiple style elements have same style ID then only first element is used', function() {
|
||||
var styles = readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
tableStyleElement("TableNormal", "Normal Table"),
|
||||
tableStyleElement("TableNormal", "Table Normal")
|
||||
])
|
||||
);
|
||||
assert.equal(styles.findTableStyleById("TableNormal").name, "Normal Table");
|
||||
});
|
||||
|
||||
test('w:type of __proto__ does not cause prototype pollution', function() {
|
||||
readStylesXml(
|
||||
new XmlElement("w:styles", {}, [
|
||||
new XmlElement("w:style", {"w:type": "__proto__", "w:styleId": "List1"}, [
|
||||
new XmlElement("w:name", {"w:val": "List 1"}, [])
|
||||
])
|
||||
])
|
||||
);
|
||||
|
||||
assert.equal({}["List1"], undefined);
|
||||
});
|
||||
|
||||
function paragraphStyleElement(id, name) {
|
||||
return styleElement("paragraph", id, name);
|
||||
}
|
||||
|
||||
function characterStyleElement(id, name) {
|
||||
return styleElement("character", id, name);
|
||||
}
|
||||
|
||||
function tableStyleElement(id, name) {
|
||||
return styleElement("table", id, name);
|
||||
}
|
||||
|
||||
function styleElement(type, id, name) {
|
||||
return new XmlElement("w:style", {"w:type": type, "w:styleId": id}, [
|
||||
new XmlElement("w:name", {"w:val": name}, [])
|
||||
]);
|
||||
}
|
||||
|
||||
function styleWithoutWNameElement(type, id) {
|
||||
return new XmlElement("w:style", {"w:type": type, "w:styleId": id}, []);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
var createBodyReader = require("../../lib/docx/body-reader").createBodyReader;
|
||||
var defaultNumbering = require("../../lib/docx/numbering-xml").defaultNumbering;
|
||||
var Styles = require("../../lib/docx/styles-reader").Styles;
|
||||
|
||||
function createBodyReaderForTests(options) {
|
||||
options = Object.create(options || {});
|
||||
options.styles = options.styles || new Styles({}, {});
|
||||
options.numbering = options.numbering || defaultNumbering;
|
||||
return createBodyReader(options);
|
||||
}
|
||||
|
||||
exports.createBodyReaderForTests = createBodyReaderForTests;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var zipfile = require("../../lib/docx/uris");
|
||||
var test = require("../test")(module);
|
||||
|
||||
|
||||
test("uriToZipEntryName", {
|
||||
"when path does not have leading slash then path is resolved relative to base": function() {
|
||||
assert.equal(
|
||||
zipfile.uriToZipEntryName("one/two", "three/four"),
|
||||
"one/two/three/four"
|
||||
);
|
||||
},
|
||||
|
||||
"when path has leading slash then base is ignored": function() {
|
||||
assert.equal(
|
||||
zipfile.uriToZipEntryName("one/two", "/three/four"),
|
||||
"three/four"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var _ = require("underscore");
|
||||
|
||||
var test = require("../test")(module);
|
||||
var html = require("../../lib/html");
|
||||
var htmlPaths = require("../../lib/styles/html-paths");
|
||||
|
||||
var nonFreshElement = html.nonFreshElement;
|
||||
var text = html.text;
|
||||
|
||||
test("empty text nodes are removed", function() {
|
||||
assert.deepEqual(
|
||||
simplifyNode(text("")),
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
test("elements with no children are removed", function() {
|
||||
assert.deepEqual(
|
||||
simplifyNode(nonFreshElement("p", {}, [])),
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
test("elements only containing empty nodes are removed", function() {
|
||||
assert.deepEqual(
|
||||
simplifyNode(nonFreshElement("p", {}, [text("")])),
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
test("empty children of element are removed", function() {
|
||||
assert.deepEqual(
|
||||
simplifyNode(nonFreshElement("p", {}, [text("Hello"), text("")])),
|
||||
[nonFreshElement("p", {}, [text("Hello")])]
|
||||
);
|
||||
});
|
||||
|
||||
test("empty void elements are not removed", function() {
|
||||
assert.deepEqual(
|
||||
simplifyNode(nonFreshElement("br", {}, [])),
|
||||
[nonFreshElement("br", {}, [])]
|
||||
);
|
||||
});
|
||||
|
||||
test("successive fresh elements are not collapsed", function() {
|
||||
var path = htmlPaths.elements([
|
||||
htmlPaths.element("p", {}, {fresh: true})
|
||||
]);
|
||||
var original = concat(
|
||||
pathToNodes(path, [text("Hello")]),
|
||||
pathToNodes(path, [text(" there")])
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
html.simplify(original),
|
||||
original);
|
||||
});
|
||||
|
||||
test("successive plain non-fresh elements are collapsed if they have the same tag name", function() {
|
||||
var path = htmlPaths.elements([
|
||||
htmlPaths.element("p", {}, {fresh: false})
|
||||
]);
|
||||
assert.deepEqual(
|
||||
html.simplify(concat(
|
||||
pathToNodes(path, [text("Hello")]),
|
||||
pathToNodes(path, [text(" there")])
|
||||
)),
|
||||
pathToNodes(path, [text("Hello"), text(" there")])
|
||||
);
|
||||
});
|
||||
|
||||
test("non-fresh can collapse into preceding fresh element", function() {
|
||||
var freshPath = htmlPaths.elements([
|
||||
htmlPaths.element("p", {}, {fresh: true})]);
|
||||
var nonFreshPath = htmlPaths.elements([
|
||||
htmlPaths.element("p", {}, {fresh: false})]);
|
||||
assert.deepEqual(
|
||||
html.simplify(concat(
|
||||
pathToNodes(freshPath, [text("Hello")]),
|
||||
pathToNodes(nonFreshPath, [text(" there")])
|
||||
)),
|
||||
pathToNodes(freshPath, [text("Hello"), text(" there")])
|
||||
);
|
||||
});
|
||||
|
||||
test("children of collapsed element can collapse with children of another collapsed element", function() {
|
||||
assert.deepEqual(
|
||||
html.simplify([
|
||||
nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("Hello")])]),
|
||||
nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("there")])])
|
||||
]),
|
||||
[nonFreshElement("blockquote", {}, [nonFreshElement("p", {}, [text("Hello"), text("there")])])]
|
||||
);
|
||||
});
|
||||
|
||||
test("empty elements are removed before collapsing", function() {
|
||||
var freshPath = htmlPaths.elements([
|
||||
htmlPaths.element("p", {}, {fresh: true})]);
|
||||
var nonFreshPath = htmlPaths.elements([
|
||||
htmlPaths.element("p", {}, {fresh: false})]);
|
||||
assert.deepEqual(
|
||||
html.simplify(concat(
|
||||
pathToNodes(nonFreshPath, [text("Hello")]),
|
||||
pathToNodes(freshPath, []),
|
||||
pathToNodes(nonFreshPath, [text(" there")])
|
||||
)),
|
||||
pathToNodes(nonFreshPath, [text("Hello"), text(" there")])
|
||||
);
|
||||
});
|
||||
|
||||
test("when separator is present then separator is prepended to collapsed element", function() {
|
||||
var unseparatedPath = htmlPaths.elements([
|
||||
htmlPaths.element("pre", {}, {fresh: false})
|
||||
]);
|
||||
var separatedPath = htmlPaths.elements([
|
||||
htmlPaths.element("pre", {}, {fresh: false, separator: "\n"})
|
||||
]);
|
||||
assert.deepEqual(
|
||||
html.simplify(concat(
|
||||
pathToNodes(unseparatedPath, [text("Hello")]),
|
||||
pathToNodes(separatedPath, [text(" the"), text("re")])
|
||||
)),
|
||||
pathToNodes(unseparatedPath, [text("Hello"), text("\n"), text(" the"), text("re")])
|
||||
);
|
||||
});
|
||||
|
||||
function simplifyNode(node) {
|
||||
return html.simplify([node]);
|
||||
}
|
||||
|
||||
function concat() {
|
||||
return _.flatten(arguments, true);
|
||||
}
|
||||
|
||||
function pathToNodes(path, nodes) {
|
||||
return path.wrap(function() {
|
||||
return nodes;
|
||||
});
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var test = require("../test")(module);
|
||||
var html = require("../../lib/html");
|
||||
var writers = require("../../lib/writers");
|
||||
|
||||
|
||||
test("text is HTML escaped", function() {
|
||||
assert.equal(
|
||||
generateString(html.text("<>&")),
|
||||
"<>&");
|
||||
});
|
||||
|
||||
test("double quotes outside of attributes are not escaped", function() {
|
||||
assert.equal(
|
||||
generateString(html.text('"')),
|
||||
'"');
|
||||
});
|
||||
|
||||
test("element attributes are HTML escaped", function() {
|
||||
assert.equal(
|
||||
generateString(html.freshElement("p", {"x": "<"})),
|
||||
'<p x="<"></p>');
|
||||
});
|
||||
|
||||
test("double quotes inside attributes are escaped", function() {
|
||||
assert.equal(
|
||||
generateString(html.freshElement("p", {"x": '"'})),
|
||||
'<p x="""></p>');
|
||||
});
|
||||
|
||||
test("element children are written", function() {
|
||||
assert.equal(
|
||||
generateString(html.freshElement("p", {}, [html.text("Hello")])),
|
||||
'<p>Hello</p>');
|
||||
});
|
||||
|
||||
function generateString(node) {
|
||||
var writer = writers.writer();
|
||||
html.write(writer, [node]);
|
||||
return writer.asString();
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var hamjest = require("hamjest");
|
||||
var assertThat = hamjest.assertThat;
|
||||
var contains = hamjest.contains;
|
||||
var equalTo = hamjest.equalTo;
|
||||
var hasProperties = hamjest.hasProperties;
|
||||
|
||||
var mammoth = require("../");
|
||||
var documents = require("../lib/documents");
|
||||
var promises = require("../lib/promises");
|
||||
|
||||
var test = require("./test")(module);
|
||||
|
||||
|
||||
test('mammoth.images.inline() should be an alias of mammoth.images.imgElement()', function() {
|
||||
assert.ok(mammoth.images.inline === mammoth.images.imgElement);
|
||||
});
|
||||
|
||||
|
||||
test('mammoth.images.dataUri() encodes images in base64', function() {
|
||||
var imageBuffer = new Buffer("abc");
|
||||
var image = new documents.Image({
|
||||
readImage: function(encoding) {
|
||||
return promises.resolve(imageBuffer.toString(encoding));
|
||||
},
|
||||
contentType: "image/jpeg"
|
||||
});
|
||||
|
||||
return mammoth.images.dataUri(image).then(function(result) {
|
||||
assertThat(result, contains(
|
||||
hasProperties({tag: hasProperties({attributes: {"src": "data:image/jpeg;base64,YWJj"}})})
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('mammoth.images.imgElement()', {
|
||||
'when element does not have alt text then alt attribute is not set': function() {
|
||||
var imageBuffer = new Buffer("abc");
|
||||
var image = new documents.Image({
|
||||
readImage: function(encoding) {
|
||||
return promises.resolve(imageBuffer.toString(encoding));
|
||||
},
|
||||
contentType: "image/jpeg"
|
||||
});
|
||||
|
||||
var result = mammoth.images.imgElement(function(image) {
|
||||
return {src: "<src>"};
|
||||
})(image);
|
||||
|
||||
return result.then(function(result) {
|
||||
assertThat(result, contains(
|
||||
hasProperties({
|
||||
tag: hasProperties({
|
||||
attributes: equalTo({src: "<src>"})
|
||||
})
|
||||
})
|
||||
));
|
||||
});
|
||||
},
|
||||
|
||||
'when element has alt text then alt attribute is set': function() {
|
||||
var imageBuffer = new Buffer("abc");
|
||||
var image = new documents.Image({
|
||||
readImage: function(encoding) {
|
||||
return promises.resolve(imageBuffer.toString(encoding));
|
||||
},
|
||||
contentType: "image/jpeg",
|
||||
altText: "<alt>"
|
||||
});
|
||||
|
||||
var result = mammoth.images.imgElement(function(image) {
|
||||
return {src: "<src>"};
|
||||
})(image);
|
||||
|
||||
return result.then(function(result) {
|
||||
assertThat(result, contains(
|
||||
hasProperties({
|
||||
tag: hasProperties({
|
||||
attributes: equalTo({alt: "<alt>", src: "<src>"})
|
||||
})
|
||||
})
|
||||
));
|
||||
});
|
||||
},
|
||||
|
||||
'image alt text can be overridden by alt attribute returned from function': function() {
|
||||
var imageBuffer = new Buffer("abc");
|
||||
var image = new documents.Image({
|
||||
readImage: function(encoding) {
|
||||
return promises.resolve(imageBuffer.toString(encoding));
|
||||
},
|
||||
contentType: "image/jpeg",
|
||||
altText: "<alt>"
|
||||
});
|
||||
|
||||
var result = mammoth.images.imgElement(function(image) {
|
||||
return {alt: "<alt override>", src: "<src>"};
|
||||
})(image);
|
||||
|
||||
return result.then(function(result) {
|
||||
assertThat(result, contains(
|
||||
hasProperties({
|
||||
tag: hasProperties({
|
||||
attributes: equalTo({alt: "<alt override>", src: "<src>"})
|
||||
})
|
||||
})
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("imageFilenameExtension", {
|
||||
"extension is derived from subtype of content type": function() {
|
||||
var image = new documents.Image({
|
||||
contentType: "image/gif"
|
||||
});
|
||||
|
||||
var result = mammoth.images.imageFilenameExtension(image);
|
||||
|
||||
assertThat(result, equalTo("gif"));
|
||||
},
|
||||
|
||||
"data after second slash is ignored": function() {
|
||||
var image = new documents.Image({
|
||||
contentType: "image/gif/jpeg"
|
||||
});
|
||||
|
||||
var result = mammoth.images.imageFilenameExtension(image);
|
||||
|
||||
assertThat(result, equalTo("gif"));
|
||||
},
|
||||
|
||||
"backslashes are treated as forward slashes": function() {
|
||||
var image = new documents.Image({
|
||||
contentType: "image\\gif\\..\\"
|
||||
});
|
||||
|
||||
var result = mammoth.images.imageFilenameExtension(image);
|
||||
|
||||
assertThat(result, equalTo("gif"));
|
||||
},
|
||||
|
||||
"when there is no subtype then null is returned": function() {
|
||||
var image = new documents.Image({
|
||||
contentType: "image"
|
||||
});
|
||||
|
||||
var result = mammoth.images.imageFilenameExtension(image);
|
||||
|
||||
assertThat(result, equalTo(undefined));
|
||||
}
|
||||
});
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
var assert = require("assert");
|
||||
var child_process = require("child_process"); // eslint-disable-line camelcase
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
|
||||
var temp = require('temp').track();
|
||||
|
||||
var promises = require("../lib/promises");
|
||||
var test = require("./test")(module);
|
||||
var testPath = require("./testing").testPath;
|
||||
|
||||
test("HTML is printed to stdout if output file is not set", function() {
|
||||
return runMammoth(testPath("single-paragraph.docx")).then(function(result) {
|
||||
assert.equal(result.stderrOutput, "");
|
||||
assert.equal(result.output, "<p>Walking on imported air</p>");
|
||||
});
|
||||
});
|
||||
|
||||
test("HTML is written to file if output file is set", function() {
|
||||
return createTempDir().then(function(tempDir) {
|
||||
var outputPath = path.join(tempDir, "output.html");
|
||||
return runMammoth(testPath("single-paragraph.docx"), outputPath).then(function(result) {
|
||||
assert.equal(result.stderrOutput, "");
|
||||
assert.equal(result.output, "");
|
||||
assert.equal(fs.readFileSync(outputPath, "utf8"), "<p>Walking on imported air</p>");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var imageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=";
|
||||
|
||||
test("inline images are included in output if writing to single file", function() {
|
||||
return runMammoth(testPath("tiny-picture.docx")).then(function(result) {
|
||||
assert.equal(result.stderrOutput, "");
|
||||
assert.equal(result.output, '<p><img src="data:image/png;base64,' + imageBase64 + '" /></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test("images are written to separate files if output dir is set", function() {
|
||||
return createTempDir().then(function(tempDir) {
|
||||
var outputPath = path.join(tempDir, "tiny-picture.html");
|
||||
var imagePath = path.join(tempDir, "1.png");
|
||||
return runMammoth(testPath("tiny-picture.docx"), "--output-dir", tempDir).then(function(result) {
|
||||
assert.equal(result.stderrOutput, "");
|
||||
assert.equal(result.output, "");
|
||||
assert.equal(fs.readFileSync(outputPath, "utf8"), '<p><img src="1.png" /></p>');
|
||||
assert.equal(fs.readFileSync(imagePath, "base64"), imageBase64);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("style map is used if set", function() {
|
||||
return createTempDir().then(function(tempDir) {
|
||||
var styleMapPath = path.join(tempDir, "style-map");
|
||||
fs.writeFileSync(styleMapPath, "p => span:fresh");
|
||||
return runMammoth(testPath("single-paragraph.docx"), "--style-map", styleMapPath).then(function(result) {
|
||||
assert.equal(result.stderrOutput, "");
|
||||
assert.equal(result.output, "<span>Walking on imported air</span>");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("--output-format=markdown option generate markdown output", function() {
|
||||
return runMammoth(testPath("single-paragraph.docx"), "--output-format=markdown").then(function(result) {
|
||||
assert.equal(result.stderrOutput, "");
|
||||
assert.equal(result.output, "Walking on imported air\n\n");
|
||||
});
|
||||
});
|
||||
|
||||
test("when input file does not exist then process exits with error", function() {
|
||||
return tryRunMammoth(testPath("non-existent.docx")).then(function(result) {
|
||||
assert.strictEqual(result.error.code, 2);
|
||||
assert.strictEqual(result.stdout, "");
|
||||
assert.ok(result.stderr.startsWith("Fatal Error: ENOENT"));
|
||||
});
|
||||
});
|
||||
|
||||
function runMammoth() {
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
return tryRunMammoth(args).then(function(result) {
|
||||
console.log(result.stderr); // eslint-disable-line no-console
|
||||
assert.equal(result.error, null);
|
||||
return {output: result.stdout, stderrOutput: result.stderr};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function tryRunMammoth(args) {
|
||||
var deferred = promises.defer();
|
||||
|
||||
var processArgs = ["node", "bin/mammoth"].concat(args);
|
||||
// TODO: proper escaping of args
|
||||
var command = processArgs.join(" ");
|
||||
child_process.exec(command, function(error, stdout, stderr) { // eslint-disable-line camelcase
|
||||
deferred.resolve({error: error, stdout: stdout, stderr: stderr});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function createTempDir() {
|
||||
return temp.mkdir();
|
||||
}
|
||||
+557
@@ -0,0 +1,557 @@
|
||||
var assert = require("assert");
|
||||
var path = require("path");
|
||||
var _ = require("underscore");
|
||||
|
||||
var mammoth = require("../");
|
||||
var fs = require("../lib/fs");
|
||||
var results = require("../lib/results");
|
||||
|
||||
var testing = require("./testing");
|
||||
var test = require("./test")(module);
|
||||
var testData = testing.testData;
|
||||
var createFakeDocxFile = testing.createFakeDocxFile;
|
||||
|
||||
|
||||
test('should convert docx containing one paragraph to single p element', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, "<p>Walking on imported air</p>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('should convert docx represented by a Buffer', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return fs.readFile(docxPath)
|
||||
.then(function(buffer) {
|
||||
return mammoth.convertToHtml({buffer: buffer});
|
||||
})
|
||||
.then(function(result) {
|
||||
assert.equal(result.value, "<p>Walking on imported air</p>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('should read docx xml files with unicode byte order mark', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/utf8-bom.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, "<p>This XML has a byte order mark.</p>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('empty paragraphs are ignored by default', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/empty.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, "");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('empty paragraphs are preserved if ignoreEmptyParagraphs is false', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/empty.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}, {ignoreEmptyParagraphs: false}).then(function(result) {
|
||||
assert.equal(result.value, "<p></p>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('style map can be expressed as string', function() {
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document.xml": testData("simple/word/document.xml")
|
||||
});
|
||||
var options = {
|
||||
styleMap: "p => h1"
|
||||
};
|
||||
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
|
||||
assert.equal("<h1>Hello.</h1>", result.value);
|
||||
});
|
||||
});
|
||||
|
||||
test('style map can be expressed as array of style mappings', function() {
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document.xml": testData("simple/word/document.xml")
|
||||
});
|
||||
var options = {
|
||||
styleMap: ["p => h1"]
|
||||
};
|
||||
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
|
||||
assert.equal("<h1>Hello.</h1>", result.value);
|
||||
});
|
||||
});
|
||||
|
||||
test('embedded style map is used if present', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/embedded-style-map.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, "<h1>Walking on imported air</h1>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('explicit style map takes precedence over embedded style map', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/embedded-style-map.docx");
|
||||
var options = {
|
||||
styleMap: ["p => p"]
|
||||
};
|
||||
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
|
||||
assert.equal(result.value, "<p>Walking on imported air</p>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('explicit style map is combined with embedded style map', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/embedded-style-map.docx");
|
||||
var options = {
|
||||
styleMap: ["r => strong"]
|
||||
};
|
||||
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
|
||||
assert.equal(result.value, "<h1><strong>Walking on imported air</strong></h1>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('embedded style maps can be disabled', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/embedded-style-map.docx");
|
||||
var options = {
|
||||
includeEmbeddedStyleMap: false
|
||||
};
|
||||
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
|
||||
assert.equal(result.value, "<p>Walking on imported air</p>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('embedded style map can be written using toBuffer() and then read', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return fs.readFile(docxPath)
|
||||
.then(function(buffer) {
|
||||
return mammoth.embedStyleMap({buffer: buffer}, "p => h1");
|
||||
})
|
||||
.then(function(docx) {
|
||||
var buffer = docx.toBuffer();
|
||||
assert.ok(Buffer.isBuffer(buffer));
|
||||
return mammoth.convertToHtml({buffer: buffer});
|
||||
})
|
||||
.then(function(result) {
|
||||
assert.equal(result.value, "<h1>Walking on imported air</h1>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('embedded style map can be written using toArrayBuffer() and then read', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return fs.readFile(docxPath)
|
||||
.then(function(buffer) {
|
||||
return mammoth.embedStyleMap({buffer: buffer}, "p => h1");
|
||||
})
|
||||
.then(function(docx) {
|
||||
var arrayBuffer = docx.toArrayBuffer();
|
||||
assert.ok(!Buffer.isBuffer(arrayBuffer));
|
||||
return mammoth.convertToHtml({buffer: Buffer.from(arrayBuffer)});
|
||||
})
|
||||
.then(function(result) {
|
||||
assert.equal(result.value, "<h1>Walking on imported air</h1>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('embedded style map can be retrieved', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return fs.readFile(docxPath)
|
||||
.then(function(buffer) {
|
||||
return mammoth.embedStyleMap({buffer: buffer}, "p => h1");
|
||||
})
|
||||
.then(function(docx) {
|
||||
return mammoth.readEmbeddedStyleMap({buffer: docx.toBuffer()});
|
||||
})
|
||||
.then(function(styleMap) {
|
||||
assert.equal(styleMap, "p => h1");
|
||||
});
|
||||
});
|
||||
|
||||
test('warning if style mapping is not understood', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
var options = {
|
||||
styleMap: "????\np => h1"
|
||||
};
|
||||
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
|
||||
assert.equal("<h1>Walking on imported air</h1>", result.value);
|
||||
var warning = "Did not understand this style mapping, so ignored it: ????\n" +
|
||||
'Error was at character number 1: Expected element type but got unrecognisedCharacter "?"';
|
||||
assert.deepEqual(result.messages, [results.warning(warning)]);
|
||||
});
|
||||
});
|
||||
|
||||
test('options are passed to document converter when calling mammoth.convertToHtml', function() {
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document.xml": testData("simple/word/document.xml")
|
||||
});
|
||||
var options = {
|
||||
styleMap: "p => h1"
|
||||
};
|
||||
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
|
||||
assert.equal("<h1>Hello.</h1>", result.value);
|
||||
});
|
||||
});
|
||||
|
||||
test('options.transformDocument is used to transform document if set', function() {
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document.xml": testData("simple/word/document.xml")
|
||||
});
|
||||
var options = {
|
||||
transformDocument: function(document) {
|
||||
document.children[0].styleId = "Heading1";
|
||||
return document;
|
||||
}
|
||||
};
|
||||
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
|
||||
assert.equal("<h1>Hello.</h1>", result.value);
|
||||
});
|
||||
});
|
||||
|
||||
test('mammoth.transforms.paragraph only transforms paragraphs', function() {
|
||||
var docxFile = createFakeDocxFile({
|
||||
"word/document.xml": testData("simple/word/document.xml")
|
||||
});
|
||||
var options = {
|
||||
transformDocument: mammoth.transforms.paragraph(function(paragraph) {
|
||||
return _.extend(paragraph, {styleId: "Heading1"});
|
||||
})
|
||||
};
|
||||
return mammoth.convertToHtml({file: docxFile}, options).then(function(result) {
|
||||
assert.equal("<h1>Hello.</h1>", result.value);
|
||||
});
|
||||
});
|
||||
|
||||
test('inline images referenced by path relative to part are included in output', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('inline images referenced by path relative to base are included in output', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/tiny-picture-target-base-relative.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('src of inline images can be changed using read("base64")', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
|
||||
var convertImage = mammoth.images.imgElement(function(element) {
|
||||
return element.read("base64").then(function(encodedImage) {
|
||||
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
|
||||
});
|
||||
});
|
||||
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
|
||||
assert.deepEqual(result.messages, []);
|
||||
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('src of inline images can be changed using readAsBase64String()', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
|
||||
var convertImage = mammoth.images.imgElement(function(element) {
|
||||
return element.readAsBase64String().then(function(encodedImage) {
|
||||
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
|
||||
});
|
||||
});
|
||||
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
|
||||
assert.deepEqual(result.messages, []);
|
||||
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('src of inline images can be changed using readAsArrayBuffer()', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
|
||||
var convertImage = mammoth.images.imgElement(function(element) {
|
||||
return element.readAsArrayBuffer().then(function(arrayBuffer) {
|
||||
assert.ok(!Buffer.isBuffer(arrayBuffer));
|
||||
var encodedImage = Buffer.from(arrayBuffer).toString("base64");
|
||||
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
|
||||
});
|
||||
});
|
||||
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
|
||||
assert.deepEqual(result.messages, []);
|
||||
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('src of inline images can be changed using read()', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
|
||||
var convertImage = mammoth.images.imgElement(function(element) {
|
||||
return element.read().then(function(buffer) {
|
||||
assert.ok(Buffer.isBuffer(buffer));
|
||||
var encodedImage = buffer.toString("base64");
|
||||
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
|
||||
});
|
||||
});
|
||||
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
|
||||
assert.deepEqual(result.messages, []);
|
||||
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('src of inline images can be changed using readAsBuffer()', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/tiny-picture.docx");
|
||||
var convertImage = mammoth.images.imgElement(function(element) {
|
||||
return element.readAsBuffer().then(function(buffer) {
|
||||
assert.ok(Buffer.isBuffer(buffer));
|
||||
var encodedImage = buffer.toString("base64");
|
||||
return {src: encodedImage.substring(0, 2) + "," + element.contentType};
|
||||
});
|
||||
});
|
||||
return mammoth.convertToHtml({path: docxPath}, {convertImage: convertImage}).then(function(result) {
|
||||
assert.deepEqual(result.messages, []);
|
||||
assert.equal(result.value, '<p><img src="iV,image/png" /></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('when external file access is enabled then images stored outside of document are included in output', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/external-picture.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}, {externalFileAccess: true}).then(function(result) {
|
||||
assert.equal(result.value, '<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAOvgAADr4B6kKxwAAAABNJREFUKFNj/M+ADzDhlWUYqdIAQSwBE8U+X40AAAAASUVORK5CYII=" /></p>');
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('when external file access is enabled then error if images stored outside of document are specified when passing file without path', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/external-picture.docx");
|
||||
var buffer = fs.readFileSync(docxPath);
|
||||
return mammoth.convertToHtml({buffer: buffer}, {externalFileAccess: true}).then(function(result) {
|
||||
assert.equal(result.value, '');
|
||||
assert.equal(result.messages[0].message, "could not find external image 'tiny-picture.png', path of input document is unknown");
|
||||
assert.equal(result.messages[0].type, "error");
|
||||
});
|
||||
});
|
||||
|
||||
test('given external file access is disabled by default then error if images stored outside of document are specified', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/external-picture.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, '');
|
||||
assert.equal(result.messages[0].message, "could not read external image 'tiny-picture.png', external file access is disabled");
|
||||
assert.equal(result.messages[0].type, "error");
|
||||
});
|
||||
});
|
||||
|
||||
test('simple list is converted to list elements', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/simple-list.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, '<ul><li>Apple</li><li>Banana</li></ul>');
|
||||
});
|
||||
});
|
||||
|
||||
test('word tables are converted to html tables', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/tables.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
var expectedHtml = "<p>Above</p>" +
|
||||
"<table>" +
|
||||
"<tr><td><p>Top left</p></td><td><p>Top right</p></td></tr>" +
|
||||
"<tr><td><p>Bottom left</p></td><td><p>Bottom right</p></td></tr>" +
|
||||
"</table>" +
|
||||
"<p>Below</p>";
|
||||
assert.equal(result.value, expectedHtml);
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('footnotes are appended to text', function() {
|
||||
// TODO: don't duplicate footnotes with multiple references
|
||||
var docxPath = path.join(__dirname, "test-data/footnotes.docx");
|
||||
var options = {
|
||||
idPrefix: "doc-42-"
|
||||
};
|
||||
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
|
||||
var expectedOutput = '<p>Ouch' +
|
||||
'<sup><a href="#doc-42-footnote-1" id="doc-42-footnote-ref-1">[1]</a></sup>.' +
|
||||
'<sup><a href="#doc-42-footnote-2" id="doc-42-footnote-ref-2">[2]</a></sup></p>' +
|
||||
'<ol><li id="doc-42-footnote-1"><p> A tachyon walks into a bar. <a href="#doc-42-footnote-ref-1">↑</a></p></li>' +
|
||||
'<li id="doc-42-footnote-2"><p> Fin. <a href="#doc-42-footnote-ref-2">↑</a></p></li></ol>';
|
||||
assert.equal(result.value, expectedOutput);
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('endnotes are appended to text', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/endnotes.docx");
|
||||
var options = {
|
||||
idPrefix: "doc-42-"
|
||||
};
|
||||
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
|
||||
var expectedOutput = '<p>Ouch' +
|
||||
'<sup><a href="#doc-42-endnote-2" id="doc-42-endnote-ref-2">[1]</a></sup>.' +
|
||||
'<sup><a href="#doc-42-endnote-3" id="doc-42-endnote-ref-3">[2]</a></sup></p>' +
|
||||
'<ol><li id="doc-42-endnote-2"><p> A tachyon walks into a bar. <a href="#doc-42-endnote-ref-2">↑</a></p></li>' +
|
||||
'<li id="doc-42-endnote-3"><p> Fin. <a href="#doc-42-endnote-ref-3">↑</a></p></li></ol>';
|
||||
assert.equal(result.value, expectedOutput);
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('relationships are handled properly in footnotes', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/footnote-hyperlink.docx");
|
||||
var options = {
|
||||
idPrefix: "doc-42-"
|
||||
};
|
||||
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
|
||||
var expectedOutput =
|
||||
'<p><sup><a href="#doc-42-footnote-1" id="doc-42-footnote-ref-1">[1]</a></sup></p>' +
|
||||
'<ol><li id="doc-42-footnote-1"><p> <a href="http://www.example.com">Example</a> <a href="#doc-42-footnote-ref-1">↑</a></p></li></ol>';
|
||||
assert.equal(result.value, expectedOutput);
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('when style mapping is defined for comment references then comments are included', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/comments.docx");
|
||||
var options = {
|
||||
idPrefix: "doc-42-",
|
||||
styleMap: "comment-reference => sup"
|
||||
};
|
||||
return mammoth.convertToHtml({path: docxPath}, options).then(function(result) {
|
||||
var expectedOutput = (
|
||||
'<p>Ouch' +
|
||||
'<sup><a href="#doc-42-comment-0" id="doc-42-comment-ref-0">[MW1]</a></sup>.' +
|
||||
'<sup><a href="#doc-42-comment-2" id="doc-42-comment-ref-2">[MW2]</a></sup></p>' +
|
||||
'<dl><dt id="doc-42-comment-0">Comment [MW1]</dt><dd><p>A tachyon walks into a bar. <a href="#doc-42-comment-ref-0">↑</a></p></dd>' +
|
||||
'<dt id="doc-42-comment-2">Comment [MW2]</dt><dd><p>Fin. <a href="#doc-42-comment-ref-2">↑</a></p></dd></dl>'
|
||||
);
|
||||
assert.equal(result.value, expectedOutput);
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('textboxes are read', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/text-box.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
var expectedOutput = '<p>Datum plane</p>';
|
||||
assert.equal(result.value, expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
test('underline is ignored by default', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/underline.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, '<p><strong>The Sunset Tree</strong></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('underline can be configured with style mapping', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/underline.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}, {styleMap: "u => em"}).then(function(result) {
|
||||
assert.equal(result.value, '<p><strong>The <em>Sunset</em> Tree</strong></p>');
|
||||
});
|
||||
});
|
||||
|
||||
test('strikethrough is converted to <s> by default', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/strikethrough.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, "<p><s>Today's Special: Salmon</s> Sold out</p>");
|
||||
});
|
||||
});
|
||||
|
||||
test('strikethrough conversion can be configured with style mappings', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/strikethrough.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}, {styleMap: "strike => del"}).then(function(result) {
|
||||
assert.equal(result.value, "<p><del>Today's Special: Salmon</del> Sold out</p>");
|
||||
});
|
||||
});
|
||||
|
||||
test('indentation is used if prettyPrint is true', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}, {prettyPrint: true}).then(function(result) {
|
||||
assert.equal(result.value, "<p>\n Walking on imported air\n</p>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('using styleMapping throws error', function() {
|
||||
try {
|
||||
mammoth.styleMapping();
|
||||
} catch (error) {
|
||||
assert.equal(
|
||||
error.message,
|
||||
'Use a raw string instead of mammoth.styleMapping e.g. "p[style-name=\'Title\'] => h1" instead of mammoth.styleMapping("p[style-name=\'Title\'] => h1")'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('can convert single paragraph to markdown', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return mammoth.convertToMarkdown({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, "Walking on imported air\n\n");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('extractRawText only retains raw text', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/simple-list.docx");
|
||||
return mammoth.extractRawText({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, 'Apple\n\nBanana\n\n');
|
||||
});
|
||||
});
|
||||
|
||||
test('extractRawText can use .docx files represented by a Buffer', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return fs.readFile(docxPath)
|
||||
.then(function(buffer) {
|
||||
return mammoth.extractRawText({buffer: buffer});
|
||||
})
|
||||
.then(function(result) {
|
||||
assert.equal(result.value, "Walking on imported air\n\n");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('can read strict format', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/strict-format.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.equal(result.value, "<p>Test</p>");
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw error if file is not a valid docx document', function() {
|
||||
var docxPath = path.join(__dirname, "test-data/empty.zip");
|
||||
return mammoth.convertToHtml({path: docxPath}).then(function(result) {
|
||||
assert.ok(false, "Expected error");
|
||||
}, function(error) {
|
||||
assert.equal(error.message, "Could not find main document part. Are you sure this is a valid .docx file?");
|
||||
});
|
||||
});
|
||||
|
||||
test("Promise.done() is available", {
|
||||
"on convertToHtml()": function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return mammoth.convertToHtml({path: docxPath}).done();
|
||||
},
|
||||
|
||||
"on convertToMarkdown()": function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return mammoth.convertToMarkdown({path: docxPath}).done();
|
||||
},
|
||||
|
||||
"on extractRawText()": function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return mammoth.extractRawText({path: docxPath}).done();
|
||||
},
|
||||
|
||||
"on embedStyleMap()": function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return fs.readFile(docxPath).then(function(buffer) {
|
||||
return mammoth.embedStyleMap({buffer: buffer}, "p => h1").done();
|
||||
});
|
||||
},
|
||||
|
||||
"on readEmbeddedStyleMap()": function() {
|
||||
var docxPath = path.join(__dirname, "test-data/single-paragraph.docx");
|
||||
return fs.readFile(docxPath)
|
||||
.then(function(buffer) {
|
||||
return mammoth.embedStyleMap({buffer: buffer}, "p => h1");
|
||||
})
|
||||
.then(function(docx) {
|
||||
return mammoth.readEmbeddedStyleMap({buffer: docx.toBuffer()}).done();
|
||||
});
|
||||
}
|
||||
});
|
||||
+1
@@ -0,0 +1 @@
|
||||
--ui exports
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var _ = require("underscore");
|
||||
|
||||
var optionsReader = require("../lib/options-reader");
|
||||
var standardOptions = optionsReader._standardOptions;
|
||||
var readOptions = optionsReader.readOptions;
|
||||
var test = require("./test")(module);
|
||||
|
||||
|
||||
test('standard options are used if options is undefined', function() {
|
||||
var options = readOptions(undefined);
|
||||
assert.deepEqual(standardOptions, _.omit(options, "customStyleMap", "readStyleMap"));
|
||||
assert.deepEqual(options.customStyleMap, []);
|
||||
});
|
||||
|
||||
test('standard options are used if options is empty', function() {
|
||||
var options = readOptions({});
|
||||
assert.deepEqual(standardOptions, _.omit(options, "customStyleMap", "readStyleMap"));
|
||||
assert.deepEqual(options.customStyleMap, []);
|
||||
});
|
||||
|
||||
test('custom style map as string is prepended to standard style map', function() {
|
||||
var options = readOptions({
|
||||
styleMap: "p.SectionTitle => h2"
|
||||
});
|
||||
var styleMap = options.readStyleMap();
|
||||
assert.deepEqual("p.SectionTitle => h2", styleMap[0]);
|
||||
assert.deepEqual(optionsReader._defaultStyleMap, styleMap.slice(1));
|
||||
});
|
||||
|
||||
test('custom style map as array is prepended to standard style map', function() {
|
||||
var options = readOptions({
|
||||
styleMap: ["p.SectionTitle => h2"]
|
||||
});
|
||||
var styleMap = options.readStyleMap();
|
||||
assert.deepEqual("p.SectionTitle => h2", styleMap[0]);
|
||||
assert.deepEqual(optionsReader._defaultStyleMap, styleMap.slice(1));
|
||||
});
|
||||
|
||||
test('lines starting with # in custom style map are ignored', function() {
|
||||
var options = readOptions({
|
||||
styleMap: "# p.SectionTitle => h3\np.SectionTitle => h2"
|
||||
});
|
||||
var styleMap = options.readStyleMap();
|
||||
assert.deepEqual("p.SectionTitle => h2", styleMap[0]);
|
||||
assert.deepEqual(optionsReader._defaultStyleMap, styleMap.slice(1));
|
||||
});
|
||||
|
||||
test('blank lines in custom style map are ignored', function() {
|
||||
var options = readOptions({
|
||||
styleMap: "\n\n"
|
||||
});
|
||||
assert.deepEqual(optionsReader._defaultStyleMap, options.readStyleMap());
|
||||
});
|
||||
|
||||
test('default style mappings are ignored if includeDefaultStyleMap is false', function() {
|
||||
var options = readOptions({
|
||||
styleMap: "p.SectionTitle => h2",
|
||||
includeDefaultStyleMap: false
|
||||
});
|
||||
assert.deepEqual(["p.SectionTitle => h2"], options.readStyleMap());
|
||||
});
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var promises = require("../lib/promises");
|
||||
var test = require("./test")(module);
|
||||
|
||||
test("forEachSeries", {
|
||||
"empty array resolves immediately without calling function": function() {
|
||||
return promises.forEachSeries([], function() {
|
||||
throw new Error();
|
||||
}).then(function(result) {
|
||||
assert.deepStrictEqual(result, undefined);
|
||||
});
|
||||
},
|
||||
|
||||
"function is called on element after the previous element has been processed": function() {
|
||||
var log = [];
|
||||
|
||||
return promises.forEachSeries([1, 2, 3], function(element) {
|
||||
return new promises.Promise(function(resolve) {
|
||||
log.push(["start", element]);
|
||||
setTimeout(function() {
|
||||
log.push(["end", element]);
|
||||
resolve();
|
||||
}, 0);
|
||||
});
|
||||
}).then(function(result) {
|
||||
assert.deepStrictEqual(result, undefined);
|
||||
assert.deepStrictEqual(log, [
|
||||
["start", 1],
|
||||
["end", 1],
|
||||
["start", 2],
|
||||
["end", 2],
|
||||
["start", 3],
|
||||
["end", 3]
|
||||
]);
|
||||
});
|
||||
},
|
||||
|
||||
"processing stops on sync error": function() {
|
||||
var log = [];
|
||||
|
||||
return promises.forEachSeries([1, 2, 3], function(element) {
|
||||
return new promises.Promise(function(resolve) {
|
||||
log.push(["start", element]);
|
||||
if (element === 2) {
|
||||
throw new Error("failure");
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
log.push(["end", element]);
|
||||
resolve();
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}).then(
|
||||
function() {
|
||||
assert.fail("Expected rejection");
|
||||
},
|
||||
function(error) {
|
||||
assert.strictEqual(error.message, "failure");
|
||||
assert.deepStrictEqual(log, [
|
||||
["start", 1],
|
||||
["end", 1],
|
||||
["start", 2]
|
||||
]);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
"processing stops on async error": function() {
|
||||
var log = [];
|
||||
|
||||
return promises.forEachSeries([1, 2, 3], function(element) {
|
||||
return new promises.Promise(function(resolve, reject) {
|
||||
log.push(["start", element]);
|
||||
setTimeout(function() {
|
||||
if (element === 2) {
|
||||
reject(new Error("failure"));
|
||||
} else {
|
||||
log.push(["end", element]);
|
||||
resolve();
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
}).then(
|
||||
function() {
|
||||
assert.fail("Expected rejection");
|
||||
},
|
||||
function(error) {
|
||||
assert.strictEqual(error.message, "failure");
|
||||
assert.deepStrictEqual(log, [
|
||||
["start", 1],
|
||||
["end", 1],
|
||||
["start", 2]
|
||||
]);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("try", {
|
||||
"when function succeeds with non-promise then promise is resolved": function() {
|
||||
return promises.try(function() {
|
||||
return "success";
|
||||
}).then(function(result) {
|
||||
assert.strictEqual(result, "success");
|
||||
});
|
||||
},
|
||||
|
||||
"when function succeeds with promise then promise is resolved": function() {
|
||||
return promises.try(function() {
|
||||
return promises.resolve("success");
|
||||
}).then(function(result) {
|
||||
assert.strictEqual(result, "success");
|
||||
});
|
||||
},
|
||||
|
||||
"when function throws error then promise is rejected": function() {
|
||||
return promises.try(function() {
|
||||
throw new Error("failure");
|
||||
}).then(
|
||||
function() {
|
||||
assert.fail("Expected rejection");
|
||||
},
|
||||
function(error) {
|
||||
assert.strictEqual(error.message, "failure");
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
"when function fails with promise then promise is rejected": function() {
|
||||
return promises.try(function() {
|
||||
return promises.reject(new Error("failure"));
|
||||
}).then(
|
||||
function() {
|
||||
assert.fail("Expected rejection");
|
||||
},
|
||||
function(error) {
|
||||
assert.strictEqual(error.message, "failure");
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("props", {
|
||||
"props({}) resolve to {}": function() {
|
||||
return promises.props({}).then(function(result) {
|
||||
assert.deepStrictEqual(result, {});
|
||||
});
|
||||
},
|
||||
|
||||
"when object has promise values then props returns with all values resolved": function() {
|
||||
return promises.props({
|
||||
a: promises.resolve(1),
|
||||
b: promises.resolve(2),
|
||||
c: promises.resolve(3)
|
||||
}).then(function(result) {
|
||||
assert.deepStrictEqual(result, {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
"when object has non-promise values then props returns with same values": function() {
|
||||
return promises.props({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
}).then(function(result) {
|
||||
assert.deepStrictEqual(result, {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
"when value is rejected promise then props is rejected promise": function() {
|
||||
return promises.props({
|
||||
a: promises.resolve(1),
|
||||
b: promises.reject(new Error("failure")),
|
||||
c: promises.resolve(3)
|
||||
}).then(
|
||||
function(result) {
|
||||
assert.fail("Expected rejection");
|
||||
},
|
||||
function(error) {
|
||||
assert.strictEqual(error.message, "failure");
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
"props(...).also(...) can be used to add props": function() {
|
||||
return promises.props({
|
||||
a: promises.resolve(1)
|
||||
}).also(function(result) {
|
||||
return {
|
||||
b: result.a + 1
|
||||
};
|
||||
}).then(function(result) {
|
||||
assert.deepStrictEqual(result, {
|
||||
a: 1,
|
||||
b: 2
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
"also can be used multiple times": function() {
|
||||
return promises.props({
|
||||
a: promises.resolve(1)
|
||||
}).also(function(result) {
|
||||
return {
|
||||
b: result.a + 1
|
||||
};
|
||||
}).also(function(result) {
|
||||
return {
|
||||
c: result.b + 1
|
||||
};
|
||||
}).then(function(result) {
|
||||
assert.deepStrictEqual(result, {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("toExternalPromise() returns promise with .done()", function() {
|
||||
var nativePromise = Promise.resolve("hello");
|
||||
|
||||
var externalPromise = promises.toExternalPromise(nativePromise);
|
||||
|
||||
externalPromise.done();
|
||||
|
||||
externalPromise.then().done();
|
||||
});
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var documents = require("../lib/documents");
|
||||
var test = require("./test")(module);
|
||||
var convertElementToRawText = require("../lib/raw-text").convertElementToRawText;
|
||||
|
||||
|
||||
test('text element is converted to text content', function() {
|
||||
var element = new documents.Text("Hello.");
|
||||
|
||||
var result = convertElementToRawText(element);
|
||||
|
||||
assert.strictEqual(result, "Hello.");
|
||||
});
|
||||
|
||||
test('tab element is converted to tab character', function() {
|
||||
var element = documents.tab();
|
||||
|
||||
var result = convertElementToRawText(element);
|
||||
|
||||
assert.strictEqual(result, "\t");
|
||||
});
|
||||
|
||||
test('paragraphs are terminated with newlines', function() {
|
||||
var element = new documents.Paragraph(
|
||||
[
|
||||
new documents.Text("Hello "),
|
||||
new documents.Text("world.")
|
||||
],
|
||||
{}
|
||||
);
|
||||
|
||||
var result = convertElementToRawText(element);
|
||||
|
||||
assert.strictEqual(result, "Hello world.\n\n");
|
||||
});
|
||||
|
||||
test('children are recursively converted to text', function() {
|
||||
var element = new documents.Document([
|
||||
new documents.Paragraph(
|
||||
[
|
||||
new documents.Text("Hello "),
|
||||
new documents.Text("world.")
|
||||
],
|
||||
{}
|
||||
)
|
||||
]);
|
||||
|
||||
var result = convertElementToRawText(element);
|
||||
|
||||
assert.strictEqual(result, "Hello world.\n\n");
|
||||
});
|
||||
|
||||
|
||||
test('non-text element without children is converted to empty string', function() {
|
||||
var element = documents.lineBreak;
|
||||
|
||||
var result = convertElementToRawText(element);
|
||||
|
||||
assert.strictEqual(result, "");
|
||||
});
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var results = require("../lib/results");
|
||||
var Result = results.Result;
|
||||
var test = require("./test")(module);
|
||||
|
||||
|
||||
test("Result.combine removes any duplicate messages", function() {
|
||||
var first = new Result(null, [results.warning("Warning...")]);
|
||||
var second = new Result(null, [results.warning("Warning...")]);
|
||||
|
||||
var combined = Result.combine([first, second]);
|
||||
|
||||
assert.deepEqual(combined.messages, [results.warning("Warning...")]);
|
||||
});
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
var assert = require("assert");
|
||||
var htmlPaths = require("../lib/styles/html-paths");
|
||||
var documentMatchers = require("../lib/styles/document-matchers");
|
||||
var styleReader = require("../lib/style-reader");
|
||||
var results = require("../lib/results");
|
||||
var test = require("./test")(module);
|
||||
|
||||
|
||||
var readHtmlPath = styleReader.readHtmlPath;
|
||||
var readDocumentMatcher = styleReader.readDocumentMatcher;
|
||||
var readStyle = styleReader.readStyle;
|
||||
|
||||
|
||||
test('styleReader.readHtmlPath', {
|
||||
'reads empty path': function() {
|
||||
assertHtmlPath("", htmlPaths.empty);
|
||||
},
|
||||
|
||||
'reads single element': function() {
|
||||
assertHtmlPath("p", htmlPaths.elements(["p"]));
|
||||
},
|
||||
|
||||
'reads choice of elements': function() {
|
||||
assertHtmlPath(
|
||||
"ul|ol",
|
||||
htmlPaths.elements([
|
||||
htmlPaths.element(["ul", "ol"])
|
||||
])
|
||||
);
|
||||
},
|
||||
|
||||
'reads nested elements': function() {
|
||||
assertHtmlPath("ul > li", htmlPaths.elements(["ul", "li"]));
|
||||
},
|
||||
|
||||
'reads class on element': function() {
|
||||
var expected = htmlPaths.elements([
|
||||
htmlPaths.element("p", {"class": "tip"})
|
||||
]);
|
||||
assertHtmlPath("p.tip", expected);
|
||||
},
|
||||
|
||||
'reads class with escaped colon': function() {
|
||||
var expected = htmlPaths.elements([
|
||||
htmlPaths.element("p", {"class": "a:b"})
|
||||
]);
|
||||
assertHtmlPath("p.a\\:b", expected);
|
||||
},
|
||||
|
||||
'reads multiple classes on element': function() {
|
||||
var expected = htmlPaths.elements([
|
||||
htmlPaths.element("p", {"class": "tip help"})
|
||||
]);
|
||||
assertHtmlPath("p.tip.help", expected);
|
||||
},
|
||||
|
||||
'reads attribute on element': function() {
|
||||
var expected = htmlPaths.elements([
|
||||
htmlPaths.element("p", {"lang": "fr"})
|
||||
]);
|
||||
assertHtmlPath("p[lang='fr']", expected);
|
||||
},
|
||||
|
||||
'reads multiple attributes on element': function() {
|
||||
var expected = htmlPaths.elements([
|
||||
htmlPaths.element("p", {"lang": "fr", "data-x": "y"})
|
||||
]);
|
||||
assertHtmlPath("p[lang='fr'][data-x='y']", expected);
|
||||
},
|
||||
|
||||
'reads when element must be fresh': function() {
|
||||
var expected = htmlPaths.elements([
|
||||
htmlPaths.element("p", {}, {"fresh": true})
|
||||
]);
|
||||
assertHtmlPath("p:fresh", expected);
|
||||
},
|
||||
|
||||
'reads separator for elements': function() {
|
||||
var expected = htmlPaths.elements([
|
||||
htmlPaths.element("p", {}, {separator: "x"})
|
||||
]);
|
||||
assertHtmlPath("p:separator('x')", expected);
|
||||
},
|
||||
|
||||
'reads separator with escape sequence': function() {
|
||||
var expected = htmlPaths.elements([
|
||||
htmlPaths.element("p", {}, {separator: "\r\n\t\'\\"})
|
||||
]);
|
||||
assertHtmlPath("p:separator('\\r\\n\\t\\'\\\\')", expected);
|
||||
},
|
||||
|
||||
'reads ignore element': function() {
|
||||
assertHtmlPath("!", htmlPaths.ignore);
|
||||
}
|
||||
});
|
||||
|
||||
function assertHtmlPath(input, expected) {
|
||||
assert.deepEqual(readHtmlPath(input), results.success(expected));
|
||||
}
|
||||
|
||||
test("styleReader.readDocumentMatcher", {
|
||||
"reads plain paragraph": function() {
|
||||
assertDocumentMatcher("p", documentMatchers.paragraph());
|
||||
},
|
||||
|
||||
"reads paragraph with style ID": function() {
|
||||
assertDocumentMatcher(
|
||||
"p.Heading1",
|
||||
documentMatchers.paragraph({styleId: "Heading1"})
|
||||
);
|
||||
},
|
||||
|
||||
"reads paragraph with exact style name": function() {
|
||||
assertDocumentMatcher(
|
||||
"p[style-name='Heading 1']",
|
||||
documentMatchers.paragraph({styleName: documentMatchers.equalTo("Heading 1")})
|
||||
);
|
||||
},
|
||||
|
||||
"reads paragraph with style name prefix": function() {
|
||||
assertDocumentMatcher(
|
||||
"p[style-name^='Heading']",
|
||||
documentMatchers.paragraph({styleName: documentMatchers.startsWith("Heading")})
|
||||
);
|
||||
},
|
||||
|
||||
"reads p:ordered-list(1) as ordered list with index of 0": function() {
|
||||
assertDocumentMatcher(
|
||||
"p:ordered-list(1)",
|
||||
documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 0}})
|
||||
);
|
||||
},
|
||||
|
||||
"reads p:unordered-list(1) as unordered list with index of 0": function() {
|
||||
assertDocumentMatcher(
|
||||
"p:unordered-list(1)",
|
||||
documentMatchers.paragraph({list: {isOrdered: false, levelIndex: 0}})
|
||||
);
|
||||
},
|
||||
|
||||
"reads plain run": function() {
|
||||
assertDocumentMatcher(
|
||||
"r",
|
||||
documentMatchers.run()
|
||||
);
|
||||
},
|
||||
|
||||
"reads plain table": function() {
|
||||
assertDocumentMatcher("table", documentMatchers.table());
|
||||
},
|
||||
|
||||
"reads table with style ID": function() {
|
||||
assertDocumentMatcher(
|
||||
"table.TableNormal",
|
||||
documentMatchers.table({
|
||||
styleId: "TableNormal"
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
"reads table with style name": function() {
|
||||
assertDocumentMatcher(
|
||||
"table[style-name='Normal Table']",
|
||||
documentMatchers.table({
|
||||
styleName: documentMatchers.equalTo("Normal Table")
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
"reads bold": function() {
|
||||
assertDocumentMatcher(
|
||||
"b",
|
||||
documentMatchers.bold
|
||||
);
|
||||
},
|
||||
|
||||
"reads italic": function() {
|
||||
assertDocumentMatcher(
|
||||
"i",
|
||||
documentMatchers.italic
|
||||
);
|
||||
},
|
||||
|
||||
"reads underline": function() {
|
||||
assertDocumentMatcher(
|
||||
"u",
|
||||
documentMatchers.underline
|
||||
);
|
||||
},
|
||||
|
||||
"reads strikethrough": function() {
|
||||
assertDocumentMatcher(
|
||||
"strike",
|
||||
documentMatchers.strikethrough
|
||||
);
|
||||
},
|
||||
|
||||
"reads all-caps": function() {
|
||||
assertDocumentMatcher(
|
||||
"all-caps",
|
||||
documentMatchers.allCaps
|
||||
);
|
||||
},
|
||||
|
||||
"reads small-caps": function() {
|
||||
assertDocumentMatcher(
|
||||
"small-caps",
|
||||
documentMatchers.smallCaps
|
||||
);
|
||||
},
|
||||
|
||||
"reads highlight without color": function() {
|
||||
assertDocumentMatcher(
|
||||
"highlight",
|
||||
documentMatchers.highlight()
|
||||
);
|
||||
},
|
||||
|
||||
"reads highlight with color": function() {
|
||||
assertDocumentMatcher(
|
||||
"highlight[color='yellow']",
|
||||
documentMatchers.highlight({color: "yellow"})
|
||||
);
|
||||
},
|
||||
|
||||
"reads comment-reference": function() {
|
||||
assertDocumentMatcher(
|
||||
"comment-reference",
|
||||
documentMatchers.commentReference
|
||||
);
|
||||
},
|
||||
|
||||
"reads line breaks": function() {
|
||||
assertDocumentMatcher(
|
||||
"br[type='line']",
|
||||
documentMatchers.lineBreak
|
||||
);
|
||||
},
|
||||
|
||||
"reads page breaks": function() {
|
||||
assertDocumentMatcher(
|
||||
"br[type='page']",
|
||||
documentMatchers.pageBreak
|
||||
);
|
||||
},
|
||||
|
||||
"reads column breaks": function() {
|
||||
assertDocumentMatcher(
|
||||
"br[type='column']",
|
||||
documentMatchers.columnBreak
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function assertDocumentMatcher(input, expected) {
|
||||
assert.deepEqual(readDocumentMatcher(input), results.success(expected));
|
||||
}
|
||||
|
||||
test("styleReader.read", {
|
||||
"document matcher is mapped to HTML path using arrow": function() {
|
||||
assertStyleMapping(
|
||||
"p => h1",
|
||||
{
|
||||
from: documentMatchers.paragraph(),
|
||||
to: htmlPaths.elements(["h1"])
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
"reads style mapping with no HTML path": function() {
|
||||
assertStyleMapping(
|
||||
"r =>",
|
||||
{
|
||||
from: documentMatchers.run(),
|
||||
to: htmlPaths.empty
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
"error when not all input is consumed": function() {
|
||||
assert.deepEqual(
|
||||
readStyle("r => span a"),
|
||||
new results.Result(null, [results.warning("Did not understand this style mapping, so ignored it: r => span a\nError was at character number 10: Expected end but got whitespace")])
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function assertStyleMapping(input, expected) {
|
||||
assert.deepEqual(readStyle(input), results.success(expected));
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var test = require("../test")(module);
|
||||
var documentMatchers = require("../../lib/styles/document-matchers");
|
||||
var documents = require("../../lib/documents");
|
||||
var Paragraph = documents.Paragraph;
|
||||
|
||||
test("paragraph with no options matches any paragraph", function() {
|
||||
var matcher = documentMatchers.paragraph();
|
||||
assert.ok(matcher.matches(new Paragraph()));
|
||||
assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1")));
|
||||
});
|
||||
|
||||
test("paragraph style ID only matches paragraphs with that style ID", function() {
|
||||
var matcher = documentMatchers.paragraph({styleId: "Heading1"});
|
||||
assert.ok(!matcher.matches(new Paragraph()));
|
||||
assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1")));
|
||||
assert.ok(!matcher.matches(paragraphWithStyle("Heading2", "Heading 2")));
|
||||
});
|
||||
|
||||
test("paragraph style name only matches paragraphs with that style name", function() {
|
||||
var matcher = documentMatchers.paragraph({styleName: documentMatchers.equalTo("Heading 1")});
|
||||
assert.ok(!matcher.matches(new Paragraph()));
|
||||
assert.ok(matcher.matches(paragraphWithStyle("Heading1", "Heading 1")));
|
||||
assert.ok(!matcher.matches(paragraphWithStyle("Heading2", "Heading 2")));
|
||||
});
|
||||
|
||||
test("ordered-list(index) matches an ordered list with specified level index", function() {
|
||||
var matcher = documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 1}});
|
||||
assert.ok(!matcher.matches(new Paragraph()));
|
||||
assert.ok(matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: true}})));
|
||||
assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 0, isOrdered: true}})));
|
||||
assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: false}})));
|
||||
});
|
||||
|
||||
test("unordered-list(index) matches an unordered list with specified level index", function() {
|
||||
var matcher = documentMatchers.paragraph({list: {isOrdered: false, levelIndex: 1}});
|
||||
assert.ok(!matcher.matches(new Paragraph()));
|
||||
assert.ok(matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: false}})));
|
||||
assert.ok(!matcher.matches(new Paragraph([], {numbering: {level: 1, isOrdered: true}})));
|
||||
});
|
||||
|
||||
test("matchers for lists with index 0 do not match elements that are not lists", function() {
|
||||
var matcher = documentMatchers.paragraph({list: {isOrdered: true, levelIndex: 0}});
|
||||
assert.ok(!matcher.matches(new Paragraph()));
|
||||
});
|
||||
|
||||
test("highlight matcher does not match non-highlight elements", function() {
|
||||
var matcher = documentMatchers.highlight();
|
||||
assert.ok(!matcher.matches(new Paragraph()));
|
||||
});
|
||||
|
||||
test("highlight matcher without color matches all highlight elements", function() {
|
||||
var matcher = documentMatchers.highlight({});
|
||||
assert.ok(matcher.matches({type: "highlight", color: "yellow"}));
|
||||
});
|
||||
|
||||
test("highlight matcher with color matches highlight with that color", function() {
|
||||
var matcher = documentMatchers.highlight({color: "yellow"});
|
||||
assert.ok(matcher.matches({type: "highlight", color: "yellow"}));
|
||||
});
|
||||
|
||||
test("highlight matcher with color does not match highlights with other colors", function() {
|
||||
var matcher = documentMatchers.highlight({color: "yellow"});
|
||||
assert.ok(!matcher.matches({type: "highlight", color: "red"}));
|
||||
});
|
||||
|
||||
function paragraphWithStyle(styleId, styleName) {
|
||||
return new Paragraph([], {styleId: styleId, styleName: styleName});
|
||||
}
|
||||
|
||||
|
||||
test("equalTo matcher is case insensitive", function() {
|
||||
var matcher = documentMatchers.equalTo("Heading 1");
|
||||
assert.ok(matcher.operator(matcher.operand, "heaDING 1"));
|
||||
assert.ok(!matcher.operator(matcher.operand, "heaDING 2"));
|
||||
});
|
||||
|
||||
test("startsWith matches strings with prefix", function() {
|
||||
var matcher = documentMatchers.startsWith("Heading");
|
||||
assert.ok(matcher.operator(matcher.operand, "Heading"));
|
||||
assert.ok(matcher.operator(matcher.operand, "Heading 1"));
|
||||
assert.ok(!matcher.operator(matcher.operand, "Custom Heading"));
|
||||
assert.ok(!matcher.operator(matcher.operand, "Head"));
|
||||
assert.ok(!matcher.operator(matcher.operand, "Header 2"));
|
||||
});
|
||||
|
||||
test("startsWith matcher is case insensitive", function() {
|
||||
var matcher = documentMatchers.startsWith("Heading");
|
||||
assert.ok(matcher.operator(matcher.operand, "heaDING"));
|
||||
});
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
var assert = require("assert");
|
||||
|
||||
var test = require("../test")(module);
|
||||
var htmlPaths = require("../../lib/styles/html-paths");
|
||||
|
||||
|
||||
test("element can match multiple tag names", function() {
|
||||
var pathPart = htmlPaths.element(["ul", "ol"]);
|
||||
assert.ok(pathPart.matchesElement({tagName: "ul"}));
|
||||
assert.ok(pathPart.matchesElement({tagName: "ol"}));
|
||||
assert.ok(!pathPart.matchesElement({tagName: "p"}));
|
||||
});
|
||||
|
||||
test("element matches if attributes are the same", function() {
|
||||
var pathPart = htmlPaths.element(["p"], {"class": "tip"});
|
||||
assert.ok(!pathPart.matchesElement({tagName: "p"}));
|
||||
assert.ok(!pathPart.matchesElement({tagName: "p", attributes: {"class": "tip help"}}));
|
||||
assert.ok(pathPart.matchesElement({tagName: "p", attributes: {"class": "tip"}}));
|
||||
});
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
var hamjest = require("hamjest");
|
||||
var assertThat = hamjest.assertThat;
|
||||
var contains = hamjest.contains;
|
||||
var hasProperties = hamjest.hasProperties;
|
||||
|
||||
var tokenise = require("../../../lib/styles/parser/tokeniser").tokenise;
|
||||
var test = require("../../test")(module);
|
||||
|
||||
|
||||
test("unknown tokens are tokenised", function() {
|
||||
assertTokens("~", [isToken("unrecognisedCharacter", "~")]);
|
||||
});
|
||||
|
||||
test("empty string is tokenised to end of file token", function() {
|
||||
assertTokens("", []);
|
||||
});
|
||||
|
||||
test("whitespace is tokenised", function() {
|
||||
assertTokens(" \t\t ", [isToken("whitespace")]);
|
||||
});
|
||||
|
||||
test("identifiers are tokenised", function() {
|
||||
assertTokens("Overture", [isToken("identifier", "Overture")]);
|
||||
});
|
||||
|
||||
test("integers are tokenised", function() {
|
||||
assertTokens("123", [isToken("integer", "123")]);
|
||||
});
|
||||
|
||||
test("strings are tokenised", function() {
|
||||
assertTokens("'Tristan'", [isToken("string", "Tristan")]);
|
||||
});
|
||||
|
||||
test("escaped string terminators in strings are tokenised", function() {
|
||||
assertTokens("'Tristan\\''", [isToken("string", "Tristan\\'")]);
|
||||
});
|
||||
|
||||
test("escape sequences in strings are tokenised", function() {
|
||||
assertTokens("'Tristan\\\\'", [isToken("string", "Tristan\\\\")]);
|
||||
});
|
||||
|
||||
test("unterminated strings are tokenised", function() {
|
||||
assertTokens("'Tristan", [isToken("unterminated-string", "Tristan")]);
|
||||
});
|
||||
|
||||
test("unterminated strings ending with escaped string terminator are tokenised", function() {
|
||||
assertTokens("'Tristan\\'", [isToken("unterminated-string", "Tristan\\'")]);
|
||||
});
|
||||
|
||||
test("unterminated strings with unterminated escape are tokenised", function() {
|
||||
assertTokens("'Tristan\\", [isToken("unterminated-string", "Tristan\\")]);
|
||||
});
|
||||
|
||||
test("unterminated strings with many escape sequences do not cause excessive backtracking", function() {
|
||||
assertTokens("'" + "\\a".repeat(50), [isToken("unterminated-string", "\\a".repeat(50))]);
|
||||
});
|
||||
|
||||
test("arrows are tokenised", function() {
|
||||
assertTokens("=>", [isToken("arrow")]);
|
||||
});
|
||||
|
||||
test("classes are tokenised", function() {
|
||||
assertTokens(".overture", [isToken("dot"), isToken("identifier", "overture")]);
|
||||
});
|
||||
|
||||
test("colons are tokenised", function() {
|
||||
assertTokens("::", [isToken("colon"), isToken("colon")]);
|
||||
});
|
||||
|
||||
test("greater thans are tokenised", function() {
|
||||
assertTokens(">>", [isToken("gt"), isToken("gt")]);
|
||||
});
|
||||
|
||||
test("equals are tokenised", function() {
|
||||
assertTokens("==", [isToken("equals"), isToken("equals")]);
|
||||
});
|
||||
|
||||
test("startsWith symbols are tokenised", function() {
|
||||
assertTokens("^=^=", [isToken("startsWith"), isToken("startsWith")]);
|
||||
});
|
||||
|
||||
test("open parens are tokenised", function() {
|
||||
assertTokens("((", [isToken("open-paren"), isToken("open-paren")]);
|
||||
});
|
||||
|
||||
test("close parens are tokenised", function() {
|
||||
assertTokens("))", [isToken("close-paren"), isToken("close-paren")]);
|
||||
});
|
||||
|
||||
test("open square brackets are tokenised", function() {
|
||||
assertTokens("[[", [isToken("open-square-bracket"), isToken("open-square-bracket")]);
|
||||
});
|
||||
|
||||
test("close square brackets are tokenised", function() {
|
||||
assertTokens("]]", [isToken("close-square-bracket"), isToken("close-square-bracket")]);
|
||||
});
|
||||
|
||||
test("choices are tokenised", function() {
|
||||
assertTokens("||", [isToken("choice"), isToken("choice")]);
|
||||
});
|
||||
|
||||
test("can tokenise multiple tokens", function() {
|
||||
assertTokens("The Magic Position", [
|
||||
isToken("identifier", "The"),
|
||||
isToken("whitespace"),
|
||||
isToken("identifier", "Magic"),
|
||||
isToken("whitespace"),
|
||||
isToken("identifier", "Position")
|
||||
]);
|
||||
});
|
||||
|
||||
function assertTokens(input, expectedTokens) {
|
||||
assertThat(
|
||||
tokenise(input),
|
||||
contains.apply(null, expectedTokens.concat([isToken("end", null)]))
|
||||
);
|
||||
}
|
||||
|
||||
function isToken(tokenType, value) {
|
||||
return hasProperties({
|
||||
name: tokenType,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+10
@@ -0,0 +1,10 @@
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml" />
|
||||
<Relationship Id="rId3" Type="http://schemas.microsoft.com/office/2007/relationships/stylesWithEffects" Target="stylesWithEffects.xml" />
|
||||
<Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml" />
|
||||
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" />
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml" />
|
||||
<Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="http://www.example.com" TargetMode="External" />
|
||||
<Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml" />
|
||||
<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml" />
|
||||
</Relationships>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" ?>
|
||||
<w:document mc:Ignorable="w14 wp14" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
|
||||
<w:body>
|
||||
<w:p w:rsidR="00E01ECE" w:rsidRDefault="000636A7">
|
||||
<w:hyperlink r:id="rId6" w:history="1">
|
||||
<w:r>
|
||||
<w:t>coconuts</w:t>
|
||||
</w:r>
|
||||
</w:hyperlink>
|
||||
</w:p>
|
||||
<w:sectPr w:rsidR="00E01ECE">
|
||||
<w:pgSz w:h="16838" w:w="11906"/>
|
||||
<w:pgMar w:bottom="1440" w:footer="708" w:gutter="0" w:header="708" w:left="1440" w:right="1440" w:top="1440"/>
|
||||
<w:cols w:space="708"/>
|
||||
<w:docGrid w:linePitch="360"/>
|
||||
</w:sectPr>
|
||||
</w:body>
|
||||
</w:document>
|
||||
BIN
Binary file not shown.
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" ?>
|
||||
<w:document mc:Ignorable="w14 wp14" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
|
||||
<w:body>
|
||||
<w:p w:rsidR="00E01ECE" w:rsidRDefault="000636A7">
|
||||
<w:r>
|
||||
<w:t>Hello.</w:t>
|
||||
</w:r>
|
||||
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
|
||||
<w:bookmarkEnd w:id="0"/>
|
||||
</w:p>
|
||||
<w:sectPr w:rsidR="00E01ECE">
|
||||
<w:pgSz w:h="16838" w:w="11906"/>
|
||||
<w:pgMar w:bottom="1440" w:footer="708" w:gutter="0" w:header="708" w:left="1440" w:right="1440" w:top="1440"/>
|
||||
<w:cols w:space="708"/>
|
||||
<w:docGrid w:linePitch="360"/>
|
||||
</w:sectPr>
|
||||
</w:body>
|
||||
</w:document>
|
||||
Loaded 100 of 1605 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user