Skip to content

Commit ff37f3b

Browse files
authored
fix: Ensure necessary namespaces are in patched doc (#2698)
* refactor: Extract timestamp properties In preparation for reworking DocumentAttributes. * refactor: Consolidate namespaces * fix: Ensure necessary namespaces are in patched doc Fixes #2697 * Fix tsc and ESLint errors * Fix CSpell * Add a test to fix code coverage failure
1 parent 7b9b474 commit ff37f3b

File tree

10 files changed

+129
-155
lines changed

10 files changed

+129
-155
lines changed

.cspell.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
// words - list of words to be always considered correct
99
"words": [
1010
"Abjad",
11+
"aink",
1112
"aiueo",
1213
"ATLEAST",
1314
"chosung",
1415
"clippy",
1516
"datas",
17+
"dcmitype",
18+
"dcterms",
1619
"docsify",
1720
"dolan",
1821
"execa",
@@ -32,6 +35,7 @@
3235
"panose",
3336
"rels",
3437
"rsid",
38+
"sdtdh",
3539
"twip",
3640
"twips",
3741
"Xmlable",

.github/workflows/default.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,5 @@ jobs:
6565
uses: actions/checkout@v4
6666
- name: Install Dependencies
6767
run: npm ci --force
68-
- name: Prettier
68+
- name: CSpell
6969
run: npm run cspell

src/file/core-properties/properties.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { FontOptions } from "@file/fonts/font-table";
22
import { ICommentsOptions } from "@file/paragraph/run/comment-run";
33
import { IHyphenationOptions } from "@file/settings";
44
import { ICompatibilityOptions } from "@file/settings/compatibility";
5-
import { StringContainer, XmlComponent } from "@file/xml-components";
5+
import { StringContainer, XmlAttributeComponent, XmlComponent } from "@file/xml-components";
66
import { dateTimeValue } from "@util/values";
77

88
import { ICustomPropertyOptions } from "../custom-properties";
@@ -75,15 +75,7 @@ export type IPropertiesOptions = {
7575
export class CoreProperties extends XmlComponent {
7676
public constructor(options: Omit<IPropertiesOptions, "sections">) {
7777
super("cp:coreProperties");
78-
this.root.push(
79-
new DocumentAttributes({
80-
cp: "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
81-
dc: "http://purl.org/dc/elements/1.1/",
82-
dcterms: "http://purl.org/dc/terms/",
83-
dcmitype: "http://purl.org/dc/dcmitype/",
84-
xsi: "http://www.w3.org/2001/XMLSchema-instance",
85-
}),
86-
);
78+
this.root.push(new DocumentAttributes(["cp", "dc", "dcterms", "dcmitype", "xsi"]));
8779
if (options.title) {
8880
this.root.push(new StringContainer("dc:title", options.title));
8981
}
@@ -110,11 +102,15 @@ export class CoreProperties extends XmlComponent {
110102
}
111103
}
112104

105+
class TimestampElementProperties extends XmlAttributeComponent<{ readonly type: string }> {
106+
protected readonly xmlKeys = { type: "xsi:type" };
107+
}
108+
113109
class TimestampElement extends XmlComponent {
114110
public constructor(name: string) {
115111
super(name);
116112
this.root.push(
117-
new DocumentAttributes({
113+
new TimestampElementProperties({
118114
type: "dcterms:W3CDTF",
119115
}),
120116
);
Lines changed: 51 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,60 @@
1-
import { XmlAttributeComponent } from "@file/xml-components";
1+
import { AttributeMap, XmlAttributeComponent } from "@file/xml-components";
22

33
/* cSpell:disable */
4-
export type IDocumentAttributesProperties = {
5-
readonly wpc?: string;
6-
readonly mc?: string;
7-
readonly o?: string;
8-
readonly r?: string;
9-
readonly m?: string;
10-
readonly v?: string;
11-
readonly wp14?: string;
12-
readonly wp?: string;
13-
readonly w10?: string;
14-
readonly w?: string;
15-
readonly w14?: string;
16-
readonly w15?: string;
17-
readonly wpg?: string;
18-
readonly wpi?: string;
19-
readonly wne?: string;
20-
readonly wps?: string;
21-
readonly Ignorable?: string;
22-
readonly cp?: string;
23-
readonly dc?: string;
24-
readonly dcterms?: string;
25-
readonly dcmitype?: string;
26-
readonly xsi?: string;
27-
readonly type?: string;
28-
readonly cx?: string;
29-
readonly cx1?: string;
30-
readonly cx2?: string;
31-
readonly cx3?: string;
32-
readonly cx4?: string;
33-
readonly cx5?: string;
34-
readonly cx6?: string;
35-
readonly cx7?: string;
36-
readonly cx8?: string;
37-
readonly aink?: string;
38-
readonly am3d?: string;
39-
readonly w16cex?: string;
40-
readonly w16cid?: string;
41-
readonly w16?: string;
42-
readonly w16sdtdh?: string;
43-
readonly w16se?: string;
4+
export const DocumentAttributeNamespaces = {
5+
wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
6+
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
7+
o: "urn:schemas-microsoft-com:office:office",
8+
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
9+
m: "http://schemas.openxmlformats.org/officeDocument/2006/math",
10+
v: "urn:schemas-microsoft-com:vml",
11+
wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
12+
wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
13+
w10: "urn:schemas-microsoft-com:office:word",
14+
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
15+
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
16+
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
17+
wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
18+
wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
19+
wne: "http://schemas.microsoft.com/office/word/2006/wordml",
20+
wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
21+
cp: "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
22+
dc: "http://purl.org/dc/elements/1.1/",
23+
dcterms: "http://purl.org/dc/terms/",
24+
dcmitype: "http://purl.org/dc/dcmitype/",
25+
xsi: "http://www.w3.org/2001/XMLSchema-instance",
26+
cx: "http://schemas.microsoft.com/office/drawing/2014/chartex",
27+
cx1: "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex",
28+
cx2: "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex",
29+
cx3: "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex",
30+
cx4: "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex",
31+
cx5: "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex",
32+
cx6: "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex",
33+
cx7: "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex",
34+
cx8: "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex",
35+
aink: "http://schemas.microsoft.com/office/drawing/2016/ink",
36+
am3d: "http://schemas.microsoft.com/office/drawing/2017/model3d",
37+
w16cex: "http://schemas.microsoft.com/office/word/2018/wordml/cex",
38+
w16cid: "http://schemas.microsoft.com/office/word/2016/wordml/cid",
39+
w16: "http://schemas.microsoft.com/office/word/2018/wordml",
40+
w16sdtdh: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash",
41+
w16se: "http://schemas.microsoft.com/office/word/2015/wordml/symex",
4442
};
4543
/* cSpell:enable */
4644

45+
export type DocumentAttributeNamespace = keyof typeof DocumentAttributeNamespaces;
46+
47+
export type IDocumentAttributesProperties = Partial<Record<DocumentAttributeNamespace, string>> & {
48+
readonly Ignorable?: string;
49+
};
50+
4751
export class DocumentAttributes extends XmlAttributeComponent<IDocumentAttributesProperties> {
4852
protected readonly xmlKeys = {
49-
wpc: "xmlns:wpc",
50-
mc: "xmlns:mc",
51-
o: "xmlns:o",
52-
r: "xmlns:r",
53-
m: "xmlns:m",
54-
v: "xmlns:v",
55-
wp14: "xmlns:wp14",
56-
wp: "xmlns:wp",
57-
w10: "xmlns:w10",
58-
w: "xmlns:w",
59-
w14: "xmlns:w14",
60-
w15: "xmlns:w15",
61-
wpg: "xmlns:wpg",
62-
wpi: "xmlns:wpi",
63-
wne: "xmlns:wne",
64-
wps: "xmlns:wps",
6553
Ignorable: "mc:Ignorable",
66-
cp: "xmlns:cp",
67-
dc: "xmlns:dc",
68-
dcterms: "xmlns:dcterms",
69-
dcmitype: "xmlns:dcmitype",
70-
xsi: "xmlns:xsi",
71-
type: "xsi:type",
72-
cx: "xmlns:cx",
73-
cx1: "xmlns:cx1",
74-
cx2: "xmlns:cx2",
75-
cx3: "xmlns:cx3",
76-
cx4: "xmlns:cx4",
77-
cx5: "xmlns:cx5",
78-
cx6: "xmlns:cx6",
79-
cx7: "xmlns:cx7",
80-
cx8: "xmlns:cx8",
81-
aink: "xmlns:aink",
82-
am3d: "xmlns:am3d",
83-
w16cex: "xmlns:w16cex",
84-
w16cid: "xmlns:w16cid",
85-
w16: "xmlns:w16",
86-
w16sdtdh: "xmlns:w16sdtdh",
87-
w16se: "xmlns:w16se",
88-
};
54+
...Object.fromEntries(Object.keys(DocumentAttributeNamespaces).map((key) => [key, `xmlns:${key}`])),
55+
} as AttributeMap<IDocumentAttributesProperties>;
56+
57+
public constructor(ns: readonly DocumentAttributeNamespace[], Ignorable?: string) {
58+
super({ Ignorable, ...Object.fromEntries(ns.map((n) => [n, DocumentAttributeNamespaces[n]])) });
59+
}
8960
}

src/file/document/document.ts

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -37,41 +37,43 @@ export class Document extends XmlComponent {
3737
public constructor(options: IDocumentOptions) {
3838
super("w:document");
3939
this.root.push(
40-
new DocumentAttributes({
41-
wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
42-
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
43-
o: "urn:schemas-microsoft-com:office:office",
44-
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
45-
m: "http://schemas.openxmlformats.org/officeDocument/2006/math",
46-
v: "urn:schemas-microsoft-com:vml",
47-
wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
48-
wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
49-
w10: "urn:schemas-microsoft-com:office:word",
50-
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
51-
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
52-
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
53-
wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
54-
wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
55-
wne: "http://schemas.microsoft.com/office/word/2006/wordml",
56-
wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
57-
cx: "http://schemas.microsoft.com/office/drawing/2014/chartex",
58-
cx1: "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex",
59-
cx2: "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex",
60-
cx3: "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex",
61-
cx4: "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex",
62-
cx5: "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex",
63-
cx6: "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex",
64-
cx7: "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex",
65-
cx8: "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex",
66-
aink: "http://schemas.microsoft.com/office/drawing/2016/ink",
67-
am3d: "http://schemas.microsoft.com/office/drawing/2017/model3d",
68-
w16cex: "http://schemas.microsoft.com/office/word/2018/wordml/cex",
69-
w16cid: "http://schemas.microsoft.com/office/word/2016/wordml/cid",
70-
w16: "http://schemas.microsoft.com/office/word/2018/wordml",
71-
w16sdtdh: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash",
72-
w16se: "http://schemas.microsoft.com/office/word/2015/wordml/symex",
73-
Ignorable: "w14 w15 wp14",
74-
}),
40+
new DocumentAttributes(
41+
[
42+
"wpc",
43+
"mc",
44+
"o",
45+
"r",
46+
"m",
47+
"v",
48+
"wp14",
49+
"wp",
50+
"w10",
51+
"w",
52+
"w14",
53+
"w15",
54+
"wpg",
55+
"wpi",
56+
"wne",
57+
"wps",
58+
"cx",
59+
"cx1",
60+
"cx2",
61+
"cx3",
62+
"cx4",
63+
"cx5",
64+
"cx6",
65+
"cx7",
66+
"cx8",
67+
"aink",
68+
"am3d",
69+
"w16cex",
70+
"w16cid",
71+
"w16",
72+
"w16sdtdh",
73+
"w16se",
74+
],
75+
"w14 w15 wp14",
76+
),
7577
);
7678
this.body = new Body();
7779
if (options.background) {

src/file/numbering/numbering.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,10 @@ export class Numbering extends XmlComponent {
3737
public constructor(options: INumberingOptions) {
3838
super("w:numbering");
3939
this.root.push(
40-
new DocumentAttributes({
41-
wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
42-
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
43-
o: "urn:schemas-microsoft-com:office:office",
44-
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
45-
m: "http://schemas.openxmlformats.org/officeDocument/2006/math",
46-
v: "urn:schemas-microsoft-com:vml",
47-
wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
48-
wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
49-
w10: "urn:schemas-microsoft-com:office:word",
50-
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
51-
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
52-
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
53-
wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
54-
wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
55-
wne: "http://schemas.microsoft.com/office/word/2006/wordml",
56-
wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
57-
Ignorable: "w14 w15 wp14",
58-
}),
40+
new DocumentAttributes(
41+
["wpc", "mc", "o", "r", "m", "v", "wp14", "wp", "w10", "w", "w14", "w15", "wpg", "wpi", "wne", "wps"],
42+
"w14 w15 wp14",
43+
),
5944
);
6045

6146
const abstractNumbering = new AbstractNumbering(this.abstractNumUniqueNumericId(), [

src/file/styles/external-styles-factory.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ describe("External styles factory", () => {
144144
expect(() => new ExternalStylesFactory().newInstance(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><foo/>`)).to.throw(
145145
"can not find styles element",
146146
);
147+
148+
expect(() => new ExternalStylesFactory().newInstance(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`)).to.throw(
149+
"can not find styles element",
150+
);
147151
});
148152

149153
it("should parse styles elements", () => {

src/file/styles/factory.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,7 @@ export type IDefaultStylesOptions = {
3838

3939
export class DefaultStylesFactory {
4040
public newInstance(options: IDefaultStylesOptions = {}): IStylesOptions {
41-
const documentAttributes = new DocumentAttributes({
42-
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
43-
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
44-
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
45-
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
46-
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
47-
Ignorable: "w14 w15",
48-
});
41+
const documentAttributes = new DocumentAttributes(["mc", "r", "w", "w14", "w15"], "w14 w15");
4942
return {
5043
initialStyles: documentAttributes,
5144
importedStyles: [

src/file/xml-components/default-attributes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { BaseXmlComponent, IContext } from "./base";
22
import { IXmlAttribute, IXmlableObject } from "./xmlable-object";
33

4-
type AttributeMap<T> = Record<keyof T, string>;
4+
export type AttributeMap<T> = Record<keyof T, string>;
55

66
export type AttributeData = Record<string, boolean | number | string>;
77
export type AttributePayload<T> = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } };

src/patcher/from-docx.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import JSZip from "jszip";
22
import { Element, js2xml } from "xml-js";
33

44
import { ImageReplacer } from "@export/packer/image-replacer";
5+
import { DocumentAttributeNamespaces } from "@file/document";
56
import { IViewWrapper } from "@file/document-wrapper";
67
import { File } from "@file/file";
78
import { FileChild } from "@file/file-child";
@@ -100,6 +101,24 @@ export const patchDocument = async <T extends PatchDocumentOutputType = PatchDoc
100101
}
101102

102103
const json = toJson(await value.async("text"));
104+
105+
if (key === "word/document.xml") {
106+
const document = json.elements?.find((i) => i.name === "w:document");
107+
if (document) {
108+
// We could check all namespaces from Document, but we'll instead
109+
// check only those that may be used by our element types.
110+
111+
// eslint-disable-next-line functional/immutable-data
112+
document.attributes = document.attributes ?? {};
113+
for (const ns of ["mc", "wp", "r", "w15", "m"] as const) {
114+
// eslint-disable-next-line functional/immutable-data
115+
document.attributes[`xmlns:${ns}`] = DocumentAttributeNamespaces[ns];
116+
}
117+
// eslint-disable-next-line functional/immutable-data
118+
document.attributes["mc:Ignorable"] = `${document.attributes["mc:Ignorable"] || ""} w15`.trim();
119+
}
120+
}
121+
103122
if (key.startsWith("word/") && !key.endsWith(".xml.rels")) {
104123
const context: IContext = {
105124
file,

0 commit comments

Comments
 (0)