Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,9 @@ Converts the source document to HTML.
* `ignoreEmptyParagraphs`: by default, empty paragraphs are ignored.
Set this option to `false` to preserve empty paragraphs in the output.

* `includeHeadersAndFooters`: by default, headers and footers are not included in the output.
Set this option to `True` to include them at the start and end of the output.

* `idPrefix`:
a string to prepend to any generated IDs,
such as those used by bookmarks, footnotes and endnotes.
Expand Down
4 changes: 4 additions & 0 deletions bin/mammoth
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,9 @@ parser.addArgument(["--style-map"], {
help: "File containg a style map."
});

parser.addArgument(["--include-headers-footers"], {
type: "string",
help: "Include headers and footers from the document."
});

main(parser.parseArgs());
29 changes: 26 additions & 3 deletions lib/document-to-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function DocumentConversion(options, comments) {
options = _.extend({ignoreEmptyParagraphs: true}, options);
var idPrefix = options.idPrefix === undefined ? "" : options.idPrefix;
var ignoreEmptyParagraphs = options.ignoreEmptyParagraphs;
var includeHeadersAndFooters = options.includeHeadersAndFooters;

var defaultParagraphStyle = htmlPaths.topLevelElement("p");

Expand Down Expand Up @@ -345,19 +346,39 @@ function DocumentConversion(options, comments) {
}
}

function convertHeader(headers, messages, options) {
if (!includeHeadersAndFooters) {
return [];
}

var children = convertElements(headers.children, messages, options);
return Html.freshElement("header", {}, children);
}

function convertFooter(footers, messages, options) {
if (!includeHeadersAndFooters) {
return [];
}

var children = convertElements(footers.children, messages, options);
return Html.freshElement("footer", {}, children);
}

var elementConverters = {
"document": function(document, messages, options) {
var children = convertElements(document.children, messages, options);
var notes = noteReferences.map(function(noteReference) {
return document.notes.resolve(noteReference);
});
var notesNodes = convertElements(notes, messages, options);
return children.concat([
var headers = convertElements(document.headers, messages, options);
var footers = convertElements(document.footers, messages, options);
return headers.concat(children).concat([
Html.freshElement("ol", {}, notesNodes),
Html.freshElement("dl", {}, flatMap(referencedComments, function(referencedComment) {
return convertComment(referencedComment, messages, options);
}))
]);
]).concat(footers);
},
"paragraph": convertParagraph,
"run": convertRun,
Expand Down Expand Up @@ -408,7 +429,9 @@ function DocumentConversion(options, comments) {
"table": convertTable,
"tableRow": convertTableRow,
"tableCell": convertTableCell,
"break": convertBreak
"break": convertBreak,
"header": convertHeader,
"footer": convertFooter
};
return {
convertToHtml: convertToHtml
Expand Down
26 changes: 23 additions & 3 deletions lib/documents.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ var types = exports.types = {
tableRow: "tableRow",
tableCell: "tableCell",
"break": "break",
bookmarkStart: "bookmarkStart"
bookmarkStart: "bookmarkStart",
header: "header",
footer: "footer"
};

function Document(children, options) {
options = options || {};
return {
type: types.document,
children: children,
children: children || [],
notes: options.notes || new Notes({}),
comments: options.comments || []
comments: options.comments || [],
headers: options.headers || [],
footers: options.footers || []
};
}

Expand Down Expand Up @@ -221,6 +225,20 @@ function BookmarkStart(options) {
};
}

function Header(children) {
return {
type: types.header,
children: children
};
}

function Footer(children) {
return {
type: types.footer,
children: children
};
}

exports.document = exports.Document = Document;
exports.paragraph = exports.Paragraph = Paragraph;
exports.run = exports.Run = Run;
Expand All @@ -240,5 +258,7 @@ exports.lineBreak = Break("line");
exports.pageBreak = Break("page");
exports.columnBreak = Break("column");
exports.BookmarkStart = BookmarkStart;
exports.header = exports.Header = Header;
exports.footer = exports.Footer = Footer;

exports.verticalAlignment = verticalAlignment;
4 changes: 3 additions & 1 deletion lib/docx/document-xml-reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ function DocumentXmlReader(options) {
.map(function(children) {
return new documents.Document(children, {
notes: options.notes,
comments: options.comments
comments: options.comments,
headers: options.headers,
footers: options.footers
});
});
return new Result(result.value, result.messages);
Expand Down
44 changes: 34 additions & 10 deletions lib/docx/docx-reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var numberingXml = require("./numbering-xml");
var stylesReader = require("./styles-reader");
var notesReader = require("./notes-reader");
var commentsReader = require("./comments-reader");
var extremityReader = require("./header-footer-reader");
var Files = require("./files").Files;


Expand Down Expand Up @@ -58,6 +59,20 @@ function read(docxFile, input) {
} else {
return new Result([]);
}
}),
headers: readXmlFileWithBody(result.partPaths.headers, result, function(bodyReader, xml) {
if (xml) {
return extremityReader.createHeaderReader(bodyReader)(xml);
} else {
return new Result([]);
}
}),
footers: readXmlFileWithBody(result.partPaths.footers, result, function(bodyReader, xml) {
if (xml) {
return extremityReader.createFooterReader(bodyReader)(xml);
} else {
return new Result([]);
}
})
};
}).also(function(result) {
Expand All @@ -72,12 +87,18 @@ function read(docxFile, input) {
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 result.headers.flatMap(function(headers) {
return result.footers.flatMap(function(footers) {
var reader = new DocumentXmlReader({
bodyReader: bodyReader,
notes: notes,
comments: comments,
headers: headers,
footers: footers
});
return reader.convertXmlToDocument(xml);
});
});
return reader.convertXmlToDocument(xml);
});
});
});
Expand All @@ -103,13 +124,14 @@ function findPartPaths(docxFile) {
readElement: relationshipsReader.readRelationships,
defaultValue: relationshipsReader.defaultValue
})(docxFile).then(function(documentRelationships) {
function findPartRelatedToMainDocument(name) {
function findPartRelatedToMainDocument(name, multiple) {
return findPartPath({
docxFile: docxFile,
relationships: documentRelationships,
relationshipType: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/" + name,
basePath: zipfile.splitPath(mainDocumentPath).dirname,
fallbackPath: "word/" + name + ".xml"
fallbackPath: "word/" + name + ".xml",
multiple: multiple
});
}

Expand All @@ -119,13 +141,15 @@ function findPartPaths(docxFile) {
endnotes: findPartRelatedToMainDocument("endnotes"),
footnotes: findPartRelatedToMainDocument("footnotes"),
numbering: findPartRelatedToMainDocument("numbering"),
styles: findPartRelatedToMainDocument("styles")
styles: findPartRelatedToMainDocument("styles"),
headers: findPartRelatedToMainDocument("header", true),
footers: findPartRelatedToMainDocument("footer", true)
};
});
});
}

function findPartPath(options) {
function findPartPath(options, multiple) {
var docxFile = options.docxFile;
var relationships = options.relationships;
var relationshipType = options.relationshipType;
Expand All @@ -142,7 +166,7 @@ function findPartPath(options) {
if (validTargets.length === 0) {
return fallbackPath;
} else {
return validTargets[0];
return multiple === true ? validTargets : validTargets[0];
}
}

Expand Down
21 changes: 21 additions & 0 deletions lib/docx/header-footer-reader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
var documents = require("../documents");
var Result = require("../results").Result;

exports.createHeaderReader = createReader.bind(this, documents.header);
exports.createFooterReader = createReader.bind(this, documents.footer);

function createReader(extremity, bodyReader) {
function readExtremityXml(element) {
var result = readElement(element, extremity);
return Array.isArray(result) ? Result.combine(result) : Result.combine([result]);
}

function readElement(element, extremity) {
return bodyReader.readXmlElements(element.children)
.map(function(children) {
return extremity(children);
});
}

return readExtremityXml;
}
1 change: 1 addition & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface Options {
includeDefaultStyleMap?: boolean;
convertImage?: ImageConverter;
ignoreEmptyParagraphs?: boolean;
includeHeadersAndFooters?: boolean;
idPrefix?: string;
transformDocument?: (element: any) => any;
}
Expand Down
4 changes: 3 additions & 1 deletion lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ function main(argv) {
var outputDir = argv.output_dir;
var outputFormat = argv.output_format;
var styleMapPath = argv.style_map;
var includeHeadersAndFooters = argv.include_headers_footers !== null && argv.include_headers_footers.trim() === 'true';

readStyleMap(styleMapPath).then(function(styleMap) {
var options = {
styleMap: styleMap,
outputFormat: outputFormat
outputFormat: outputFormat,
includeHeadersAndFooters: includeHeadersAndFooters
};

if (outputDir) {
Expand Down
5 changes: 4 additions & 1 deletion lib/options-reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ var defaultStyleMap = exports._defaultStyleMap = [

"r[style-name='Hyperlink'] =>",

"p[style-name='Normal'] => p:fresh"
"p[style-name='Normal'] => p:fresh",

"p.Header => p:fresh",
"p.Footer => p:fresh"
];

var standardOptions = exports._standardOptions = {
Expand Down
24 changes: 24 additions & 0 deletions test/document-to-html.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -822,3 +822,27 @@ test('when initials are blank then comment author label is blank', function() {
assert.equal(commentAuthorLabel({authorInitials: undefined}), "");
assert.equal(commentAuthorLabel({authorInitials: null}), "");
});

test('docx header is converted to <header>', function() {
var headers = [new documents.Header(
[paragraphOfText("This is a header")]
)];
var document = new documents.Document();
document.headers = headers;
var converter = new DocumentConverter({includeHeadersAndFooters: true});
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, '<header><p>This is a header</p></header>');
});
});

test('docx footer is converted to <footer>', function() {
var footers = [new documents.Footer(
[paragraphOfText("This is a footer")]
)];
var document = new documents.Document();
document.footers = footers;
var converter = new DocumentConverter({includeHeadersAndFooters: true});
return converter.convertToHtml(document).then(function(result) {
assert.equal(result.value, '<footer><p>This is a footer</p></footer>');
});
});
8 changes: 8 additions & 0 deletions test/mammoth.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,11 @@ test('should throw error if file is not a valid docx document', function() {
assert.equal(error.message, "Could not find main document part. Are you sure this is a valid .docx file?");
});
});

test('should read docx files with header and footer', function() {
var docxPath = path.join(__dirname, "test-data/header-footer.docx");
return mammoth.convertToHtml({path: docxPath}, {includeHeadersAndFooters: true}).then(function(result) {
assert.equal(result.value, "<header><p>Header text</p></header><footer><p>Footer text</p></footer>");
assert.deepEqual(result.messages, []);
});
});
Binary file added test/test-data/header-footer.docx
Binary file not shown.