-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathindex.js
124 lines (120 loc) · 4.42 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
'use strict'
// setup
const jszip = require('jszip')
const matcher = /<w:bookmarkStart.+?(?:(?:(?:w:id="(.+?)").+?(?:w:name="(.+?)"))|(?:(?:w:name="(.+?)").+?(?:w:id="(.+?)"))).+?<w:bookmarkEnd.+?(?:w:id="(\1|\4)").+?>/g
const defTags = '<w:r><w:t></w:t></w:r>'
const primitive = (v) => {
return ['string', 'number', 'boolean', 'undefined'].includes(typeof v)
}
const coal = (v, alt) => (v === 0 || v === false) ? v : (v || alt)
const charConv = {
'<': `<`,
'>': `>`,
'"': `"`,
'\'': `'`,
'&': `&`
}
const convChar = {
'<': '<',
'>': '>',
'"': '"',
''': `'`,
'&': '&'
}
const xmlCleanRgx = /(?!<|>|"|'|&)[<>"'&]/g
const xmlClean = (v) => v.replace(xmlCleanRgx, (m) => charConv[m])
const xmlDirtyRgx = /<|>|"|'|&/g
const xmlDirty = (v) => v.replace(xmlDirtyRgx, (m) => convChar[m])
// helpers
const getType = (o) => {
if (typeof o === 'string') return 'base64'
if (typeof Buffer === 'function' && o instanceof Buffer) return 'nodebuffer'
if (typeof Uint8Array === 'function' && o instanceof Uint8Array) {
return 'uint8array'
}
if (typeof ArrayBuffer === 'function' && o instanceof ArrayBuffer) {
return 'arraybuffer'
}
return 'blob'
}
const getReplacer = (marks, found, ids, fontSize) => {
return (match, id, name, name2, id2) => {
id = id || id2
name = name || name2
found[name] = true
ids.push(id)
if (!marks._dxmGetter && !marks[name]) return match
let wrap = (match.match(/(<w:r[> ].+<\/w:r>)/) || [])[0] || defTags
let [raw, val] = (wrap.match(/<w:t(?:>|\s.+?>)(.*)<\/w:t>/) || []) || []
raw = raw || '<w:t></w:t>'
val = !val ? '' : val.replace(/<.+?>/g, '')
if (marks._dxmGetter) {
marks._dxmGetter(name, xmlDirty(val))
return match
}
val = marks[name].setter(val)
let start = `<w:bookmarkStart w:id="${id}" w:name="${name}"/>`
let content = `<w:t xml:space="preserve">${val}</w:t>`
let withSize = `<w:r><w:rPr><w:sz w:val="${(fontSize || 0) * 2}"/></w:rPr>${content}</w:r>`
let end = `<w:bookmarkEnd w:id="${id}"/>`
return `${start}${wrap.replace(raw, fontSize ? withSize : content)}${end}`
}
}
// main
module.exports = (docx, marks, fontSize) => {
let append
let bookmarks = {}
if (!marks) marks = {_dxmGetter: (k, v) => { bookmarks[k] = v }}
marks = Object.assign({}, marks)
Object.entries(marks).forEach(([k, v]) => {
if (k === '_dxmGetter') return
if (primitive(v)) marks[k] = {setter: () => xmlClean(`${coal(v, '')}`)}
else if (typeof v === 'function') marks[k] = {setter: v}
else if (v.hasOwnProperty('setter') && typeof v.setter === 'string') {
marks[k] = Object.assign({}, v, {setter: () => v.setter})
} else delete marks[k]
append = append || marks[k].append
})
let type = getType(docx)
let found = {}
let ids = []
let replacer = getReplacer(marks, found, ids, fontSize)
return jszip.loadAsync(docx, {base64: type === 'base64'}).then((zip) => {
let files = Object.keys(zip.files).filter((k) => k.match(/^word\/.+\.xml$/))
let replace = (f) => {
return zip.file(f).async('string').then((text) => {
let ogTmp
let tmp = (text.match(matcher) || [])[0]
while (tmp && (tmp = (tmp.substr(1).match(matcher) || [])[0])) {
ogTmp = tmp
tmp = tmp.replace(matcher, replacer)
text = text.replace(ogTmp, tmp)
}
text = text.replace(matcher, replacer)
zip.file(f, text)
return Promise.resolve()
})
}
let finish = () => {
if (marks._dxmGetter) return Promise.resolve(bookmarks)
if (!append) return zip.generateAsync({type})
let doc = 'word/document.xml'
return zip.file(doc).async('string').then((text) => {
ids = ids.map((i) => parseInt(i, 10)).sort((a, b) => b - a)
let lastId = ids[0] || 0
Object.entries(marks).forEach(([k, v]) => {
if (found[k] || !v.append) return
let id = lastId = lastId + 1
let val = v.setter('')
let start = `<w:p><w:bookmarkStart w:id="${id}" w:name="${k}"/>`
let content = `<w:r><w:t xml:space="preserve">${val}</w:t></w:r>`
let end = `<w:bookmarkEnd w:id="${id}"/></w:p>/</w:body>`
text = text.replace(/<\/w:body>/, `${start}${content}${end}`)
})
zip.file(doc, text)
return zip.generateAsync({type})
})
}
return Promise.all(files.map(replace)).then(finish)
})
}