Skip to content

Commit 32eea1e

Browse files
committed
CCM-12776: init
1 parent 44a4b09 commit 32eea1e

File tree

4 files changed

+356
-194
lines changed

4 files changed

+356
-194
lines changed

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"clsx": "^2.1.1",
88
"date-fns": "^4.1.0",
99
"jsonwebtoken": "^9.0.2",
10+
"jszip": "^3.10.1",
1011
"jwt-decode": "^4.0.0",
1112
"markdown-it": "^13.0.2",
1213
"markdown-to-jsx": "^7.7.10",

frontend/src/app/create-and-submit-templates/page.tsx

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import DocxExtract from '@atoms/DocxExtract/DocxExtract';
12
import { NHSNotifyButton } from '@atoms/NHSNotifyButton/NHSNotifyButton';
23
import { NHSNotifyMain } from '@atoms/NHSNotifyMain/NHSNotifyMain';
34
import content from '@content/content';
@@ -17,25 +18,9 @@ export default function HomePage() {
1718
<h1 className='nhsuk-heading-xl' data-testid='page-heading'>
1819
{homePageContent.pageHeading}
1920
</h1>
20-
21-
<p>{homePageContent.text1}</p>
22-
<p>{homePageContent.text2}</p>
23-
24-
<ul className='nhsuk-list nhsuk-list--bullet'>
25-
{homePageContent.channelList.map((channel, i) => (
26-
<li key={`template${i + 1}`}>{channel}</li>
27-
))}
28-
</ul>
29-
30-
<p>{homePageContent.text3}</p>
31-
<h2 className='nhsuk-heading-l' data-testid='page-sub-heading'>
32-
{homePageContent.pageSubHeading}
33-
</h2>
34-
<p>{homePageContent.text4}</p>
35-
<p>{homePageContent.text5}</p>
36-
<p>{homePageContent.text6}</p>
37-
<p>{homePageContent.text7}</p>
38-
21+
<DocxExtract />
22+
<br />
23+
<br />
3924
<NHSNotifyButton
4025
href={homePageContent.linkButton.url}
4126
data-testid='start-now-button'
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use client';
2+
import React, { useState } from 'react';
3+
import { loadAsync } from 'jszip';
4+
5+
function normalizePath(p: string): string {
6+
// Remove any array-style indices like users[0].name -> users.name (we only care about field names)
7+
return (
8+
p
9+
// strip any embedded XML tags that may have been captured across DOCX text runs
10+
// eslint-disable-next-line sonarjs/slow-regex
11+
.replaceAll(/<[^>]+>/g, '')
12+
// remove array indices
13+
// eslint-disable-next-line sonarjs/slow-regex
14+
.replaceAll(/\[[^\]]*]/g, '')
15+
// collapse whitespace
16+
.replaceAll(/\s+/g, '')
17+
// collapse multiple dots
18+
.replaceAll(/\.+/g, '.')
19+
// trim leading/trailing dots
20+
.replace(/^\./, '')
21+
.replace(/\.$/, '')
22+
);
23+
}
24+
25+
// eslint-disable-next-line sonarjs/cognitive-complexity
26+
async function extractMarkersFromDocx(templateSource: File): Promise<string[]> {
27+
try {
28+
const ab = await templateSource.arrayBuffer();
29+
30+
// eslint-disable-next-line sonarjs/no-unsafe-unzip
31+
const zip = await loadAsync(ab);
32+
33+
const dataMarkers = new Set<string>();
34+
35+
for (const [name, entry] of Object.entries(zip.files)) {
36+
if (!/\.xml$/i.test(name)) continue;
37+
38+
const isWord = name.startsWith('word/');
39+
if (!isWord) continue;
40+
41+
let xml = '';
42+
try {
43+
xml = await entry.async('string');
44+
} catch {
45+
continue;
46+
}
47+
48+
const directRe = /{([^{]+?)}/g;
49+
let m: RegExpExecArray | null;
50+
51+
while ((m = directRe.exec(xml)) !== null) {
52+
const rawPath = m[1];
53+
if (rawPath) {
54+
const path = normalizePath(rawPath);
55+
if (!path.startsWith('d.')) continue;
56+
57+
dataMarkers.add(path.replace(/^d\./, ''));
58+
}
59+
}
60+
}
61+
62+
const markers = [...dataMarkers].filter(Boolean).sort();
63+
return markers;
64+
} catch {
65+
return [];
66+
}
67+
}
68+
69+
const DocxExtract: React.FC = () => {
70+
const [params, setParams] = useState<string[]>([]);
71+
72+
const handleFile = async (event: React.ChangeEvent<HTMLInputElement>) => {
73+
const file = event.target.files?.[0];
74+
if (!file) return;
75+
const markers = await extractMarkersFromDocx(file);
76+
77+
setParams(markers);
78+
};
79+
80+
return (
81+
<>
82+
<input type='file' accept='.docx' onChange={handleFile} />
83+
<br />
84+
<ul>
85+
{params.map((p, i) => (
86+
<li key={i}>{p}</li>
87+
))}
88+
</ul>
89+
</>
90+
);
91+
};
92+
93+
export default DocxExtract;

0 commit comments

Comments
 (0)