docxml
Version:
TypeScript (component) library for building and parsing a DOCX file
234 lines (228 loc) • 9.08 kB
JavaScript
import { create } from '../utilities/dom.js';
import { NamespaceUri, QNS } from '../utilities/namespaces.js';
import { evaluateXPathToFirstNode, evaluateXPathToMap } from '../utilities/xquery.js';
import { sectionPropertiesToNode } from './section-properties.js';
import { textPropertiesFromNode, textPropertiesToNode, } from './text-properties.js';
export function getTwipOrNull(length) {
return length && length.twip !== undefined && length.twip !== null ? length.twip : null;
}
export function paragraphPropertiesFromNode(node) {
const data = node
? // deno-lint-ignore no-explicit-any
evaluateXPathToMap(`map {
"alignment": ${QNS.w}jc/@${QNS.w}val/string(),
"outlineLvl": ${QNS.w}outlineLvl/@${QNS.w}val/number(),
"style": ${QNS.w}pStyle/@${QNS.w}val/string(),
"spacing": ${QNS.w}spacing/map {
"before": docxml:length(@${QNS.w}before, 'twip'),
"after": docxml:length(@${QNS.w}after, 'twip'),
"line": docxml:length(@${QNS.w}line, 'twip'),
"lineRule": @${QNS.w}lineRule/string(),
"afterAutoSpacing": docxml:st-on-off(@${QNS.w}afterAutoSpacing),
"beforeAutoSpacing": docxml:st-on-off(@${QNS.w}beforeAutoSpacing)
},
"indentation": ${QNS.w}ind/map {
"start": docxml:length((@${QNS.w}start|@${QNS.w}left)[1], 'twip'),
"startChars": (@${QNS.w}startChars|@${QNS.w}leftChars)[1]/number(),
"end": docxml:length((@${QNS.w}end|@${QNS.w}right)[1], 'twip'),
"endChars": (@${QNS.w}endChars|@${QNS.w}rightChars)/number(),
"hanging": docxml:length(@${QNS.w}hanging, 'twip'),
"hangingChars": @${QNS.w}hangingChars/number(),
"firstLine": docxml:length(@${QNS.w}firstLine, 'twip'),
"firstLineChars": @${QNS.w}firstLineChars/number()
},
"shading": ./${QNS.w}shd/docxml:ct-shd(.),
"borders": ./${QNS.w}pBdr/map {
"top": docxml:ct-border(${QNS.w}top),
"left": docxml:ct-border(${QNS.w}left),
"bottom": docxml:ct-border(${QNS.w}bottom),
"right": docxml:ct-border(${QNS.w}right),
"between": docxml:ct-border(${QNS.w}between)
},
"listItem": ./${QNS.w}numPr/map {
"numbering": ./${QNS.w}numId/@${QNS.w}val/number(),
"depth": ./${QNS.w}ilvl/@${QNS.w}val/number()
},
"change": ${QNS.w}pPrChange/map {
"id": @${QNS.w}id/string(),
"author": @${QNS.w}author/string(),
"date": @${QNS.w}date/string(),
"_node": ./${QNS.w}pPr
},
"tabs": ./${QNS.w}tabs/array {${QNS.w}tab/map {
"type": @${QNS.w}val/string(),
"leader": @${QNS.w}leader/string(),
"position": docxml:length(@${QNS.w}pos, 'twip')
}}
}`, node) || {}
: {};
const rpr = node && evaluateXPathToFirstNode(`./${QNS.w}rPr`, node);
if (rpr) {
data.pilcrow = textPropertiesFromNode(rpr);
}
if (data.change) {
data.change = {
...data.change,
date: new Date(data.change.date),
...paragraphPropertiesFromNode(data.change._node),
_node: undefined,
};
}
else {
delete data.change;
}
return data;
}
export function paragraphPropertiesToNode(data = {}, sectionProperties = null) {
if (!Object.keys(data).length && !sectionProperties) {
return null;
}
return create(`
element ${QNS.w}pPr {
if (exists($style)) then element ${QNS.w}pStyle {
attribute ${QNS.w}val { $style }
} else (),
if (exists($listItem)) then element ${QNS.w}numPr {
if (exists($listItem('numbering'))) then element ${QNS.w}numId {
attribute ${QNS.w}val { $listItem('numbering') }
} else (),
if (exists($listItem('depth'))) then element ${QNS.w}ilvl {
attribute ${QNS.w}val { $listItem('depth') }
} else ()
} else (),
if (exists($alignment)) then element ${QNS.w}jc {
attribute ${QNS.w}val { $alignment }
} else (),
if (exists($outlineLvl)) then element ${QNS.w}outlineLvl {
attribute ${QNS.w}val { $outlineLvl }
} else (),
docxml:ct-shd(fn:QName("${NamespaceUri.w}", "shd"), $shading),
if (exists($spacing)) then element ${QNS.w}spacing {
if (exists($spacing('before'))) then attribute ${QNS.w}before {
$spacing('before')
} else (),
if (exists($spacing('after'))) then attribute ${QNS.w}after {
$spacing('after')
} else (),
if (exists($spacing('line'))) then attribute ${QNS.w}line {
$spacing('line')
} else (),
if (exists($spacing('lineRule'))) then attribute ${QNS.w}lineRule {
$spacing('lineRule')
} else (),
if (exists($spacing('afterAutoSpacing'))) then attribute ${QNS.w}afterAutoSpacing {
$spacing('afterAutoSpacing')
} else (),
if (exists($spacing('beforeAutoSpacing'))) then attribute ${QNS.w}beforeAutoSpacing {
$spacing('beforeAutoSpacing')
} else ()
} else (),
if (exists($indentation)) then element ${QNS.w}ind {
if (exists($indentation('start'))) then attribute ${QNS.w}start {
$indentation('start')
} else (),
if (exists($indentation('startChars'))) then attribute ${QNS.w}startChars {
$indentation('startChars')
} else (),
if (exists($indentation('end'))) then attribute ${QNS.w}end {
$indentation('end')
} else (),
if (exists($indentation('endChars'))) then attribute ${QNS.w}endChars {
$indentation('endChars')
} else (),
if (exists($indentation('hanging'))) then attribute ${QNS.w}hanging {
$indentation('hanging')
} else (),
if (exists($indentation('hangingChars'))) then attribute ${QNS.w}hangingChars {
$indentation('hangingChars')
} else (),
if (exists($indentation('firstLine'))) then attribute ${QNS.w}firstLine {
$indentation('firstLine')
} else (),
if (exists($indentation('firstLineChars'))) then attribute ${QNS.w}firstLineChars {
$indentation('firstLineChars')
} else ()
} else (),
if (exists($borders)) then element ${QNS.w}pBdr {
(: In sequence order: :)
docxml:ct-border(fn:QName("${NamespaceUri.w}", "top"), $borders('top')),
docxml:ct-border(fn:QName("${NamespaceUri.w}", "left"), $borders('left')),
docxml:ct-border(fn:QName("${NamespaceUri.w}", "bottom"), $borders('bottom')),
docxml:ct-border(fn:QName("${NamespaceUri.w}", "right"), $borders('right')),
docxml:ct-border(fn:QName("${NamespaceUri.w}", "between"), $borders('between'))
} else (),
$rpr,
$sectpr,
if (exists($change)) then element ${QNS.w}pPrChange {
attribute ${QNS.w}id { $change('id') },
attribute ${QNS.w}author { $change('author') },
attribute ${QNS.w}date { $change('date') },
$change('node')
} else (),
if (exists($tabs)) then element ${QNS.w}tabs {
for $tab in array:flatten($tabs)
return element ${QNS.w}tab {
if (exists($tab('type'))) then attribute ${QNS.w}val {
$tab('type')
} else (),
if (exists($tab('leader'))) then attribute ${QNS.w}leader {
$tab('leader')
} else (),
if (exists($tab('position'))) then attribute ${QNS.w}pos {
$tab('position')
} else ()
}
} else ()
}
`, {
style: data.style || null,
alignment: data.alignment || null,
outlineLvl: data.outlineLvl === null || data.outlineLvl === undefined ? null : data.outlineLvl,
indentation: data.indentation
? {
...data.indentation,
hanging: getTwipOrNull(data.indentation.hanging),
firstLine: getTwipOrNull(data.indentation.firstLine),
start: getTwipOrNull(data.indentation.start),
end: getTwipOrNull(data.indentation.end),
}
: null,
shading: data.shading || null,
spacing: data.spacing
? {
...data.spacing,
before: getTwipOrNull(data.spacing.before),
after: getTwipOrNull(data.spacing.after),
line: getTwipOrNull(data.spacing.line),
lineRule: data.spacing.lineRule || null,
}
: null,
borders: data.borders
? {
top: null,
left: null,
bottom: null,
right: null,
between: null,
...data.borders,
}
: null,
listItem: data.listItem || null,
change: data.change
? {
id: data.change.id,
author: data.change.author,
date: data.change.date.toISOString(),
node: paragraphPropertiesToNode(data.change),
}
: null,
rpr: textPropertiesToNode(data.pilcrow || undefined),
sectpr: sectionProperties && sectionPropertiesToNode(sectionProperties),
tabs: data.tabs?.length ?
data.tabs?.map(tab => ({
type: tab.type,
leader: tab.leader,
position: getTwipOrNull(tab.position),
})) : null
});
}