Skip to content

Commit 1d0b706

Browse files
committed
feat(ooxml-validator): add support for xlsx & pptx + improve styling
1 parent 2346472 commit 1d0b706

4 files changed

Lines changed: 94 additions & 12 deletions

File tree

src/experiments/ooxml-validator/Program.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,36 @@ internal partial class SourceGenerationContext : JsonSerializerContext { }
4545
partial class WordprocessingDocumentValidator
4646
{
4747
[JSExport]
48-
public static string Validate(byte[] doc)
48+
public static string Validate(string fileName, byte[] doc)
4949
{
5050
using (MemoryStream stream = new MemoryStream())
5151
{
5252
stream.Write(doc, 0, doc.Length);
53-
using (WordprocessingDocument wordprocessingDocument = WordprocessingDocument.Open(stream, true))
53+
using (var document = getDocument(fileName, stream))
5454
{
5555
OpenXmlValidator validator = new OpenXmlValidator();
5656
ValidationError[] errors = validator
57-
.Validate(wordprocessingDocument)
57+
.Validate(document)
5858
.Select(e => new ValidationError(e))
5959
.ToArray();
6060
return JsonSerializer.Serialize(errors, typeof(ValidationError[]), SourceGenerationContext.Default);
6161
}
6262
}
6363
}
64+
65+
private static OpenXmlPackage getDocument(string fileName, Stream stream)
66+
{
67+
string fileExt = Path.GetExtension(fileName).TrimStart('.');
68+
switch (fileExt)
69+
{
70+
case "docx":
71+
return WordprocessingDocument.Open(stream, false);
72+
case "xlsx":
73+
return SpreadsheetDocument.Open(stream, false);
74+
case "pptx":
75+
return PresentationDocument.Open(stream, false);
76+
default:
77+
throw new ArgumentException("Invalid file type");
78+
}
79+
}
6480
}

src/experiments/ooxml-validator/src/index.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<!doctype html>
22
<html lang="en">
33
<head>
4-
<title>Document Format Validator</title>
4+
<title>Office Open XML Validator</title>
55
<base href="<%= base %>">
66
<meta charset="UTF-8" />
77
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
88
<script type="module" src="/src/scripts/main.ts"></script>
9+
<meta name="theme-color" content="#d83b01" />
910
<link rel="stylesheet" href="/src/styles/main.scss">
1011
<style>
1112
@font-face {
@@ -23,6 +24,10 @@
2324
font-family: 'Segoe UI Variable', sans-serif;
2425
}
2526

27+
[hidden] {
28+
display: none !important;
29+
}
30+
2631
html,
2732
body {
2833
margin: 0;
@@ -33,13 +38,13 @@
3338
</head>
3439
<body>
3540
<header>
36-
<h1>Document Format Validator</h1>
41+
<h1>Office Open XML Validator</h1>
3742
</header>
3843
<main>
3944
<form id="input">
40-
<p>Load your .docx file here</p>
45+
<p>Load your .docx, .xlsx, or .pptx file here</p>
4146
<fluent-button id="choose-file">Choose file</fluent-button>
42-
<input type="file" id="file" accept=".docx" hidden>
47+
<input type="file" id="file" accept=".docx,.xlsx,.pptx" hidden>
4348
</form>
4449
<div id="output"></div>
4550
</main>

src/experiments/ooxml-validator/src/scripts/main.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,46 @@ const errorTypes: Record<ValidationErrorType, string> = {
3232
'Markup Compatibility validation error',
3333
};
3434

35+
const header = document.querySelector<HTMLElement>('header')!;
36+
const title = header.querySelector<HTMLHeadingElement>('h1')!;
3537
const chooseFileButton =
3638
document.querySelector<HTMLButtonElement>('#choose-file')!;
39+
const inputContainer = document.querySelector<HTMLFormElement>('#input')!;
3740
const fileInput = document.querySelector<HTMLInputElement>('#file')!;
3841
const output = document.querySelector<HTMLDivElement>('#output')!;
3942

43+
// We do this dynamically based on HTML + CSS so that we don't need to
44+
// change anything on TS side if we add new file formats and/or change colors
45+
// Get the main stylesheet rules
46+
const mainStyleSheet = Array.from(document.styleSheets).find(s => !!s.href)!;
47+
const mainStyleSheetRules = Array.from(mainStyleSheet.cssRules);
48+
// Get the supported input extensions
49+
const supportedExts = fileInput.accept.split(',').map(ext => ext.slice(1));
50+
// Based on the supported extensions, get the corresponding CSS rules
51+
// For each supported extension we expect a CSS rule with the selector `header.ext`
52+
const headerExtStyleRules = mainStyleSheetRules.filter(
53+
r =>
54+
r instanceof CSSStyleRule &&
55+
supportedExts.some(ext => r.selectorText === `header.${ext}`),
56+
) as CSSStyleRule[];
57+
// Create a map of extension to color
58+
const extToColorMap = new Map(
59+
headerExtStyleRules.map(r => [
60+
r.selectorText.slice('header'.length + 1),
61+
r.style.backgroundColor,
62+
]),
63+
);
64+
// Get the meta theme color element, which we will update based on the file extension
65+
const metaThemeColor = document.querySelector<HTMLMetaElement>(
66+
'meta[name="theme-color"]',
67+
)!;
68+
4069
fileInput.addEventListener('change', async () => {
4170
if (!fileInput.files?.length) {
4271
return;
4372
}
73+
const fileName = fileInput.files[0].name;
74+
inputContainer.hidden = true;
4475
output.innerHTML = /* html */ `
4576
<div id="loading-container">
4677
<label for="loading">Loading...</label>
@@ -54,10 +85,21 @@ fileInput.addEventListener('change', async () => {
5485
reader.readAsArrayBuffer(fileInput.files[0]);
5586
const buffer = await readerResult.promise;
5687
const validationResult = exports.WordprocessingDocumentValidator.Validate(
88+
fileName,
5789
new Uint8Array(buffer),
5890
);
5991
const parsedValidationResult: ValidationError[] =
6092
JSON.parse(validationResult);
93+
const extension = fileName.slice(fileName.lastIndexOf('.') + 1);
94+
metaThemeColor.content = extToColorMap.get(extension) ?? '#000';
95+
header.className = extension;
96+
title.textContent = fileName;
97+
if (parsedValidationResult.length === 0) {
98+
output.innerHTML = /* html */ `
99+
<div style="margin: 35vh auto; text-align: center;">No errors found!</div>
100+
`;
101+
return;
102+
}
61103
const dataGrid = document.createElement('fluent-data-grid') as DataGrid;
62104
dataGrid.generateHeader = 'sticky';
63105
dataGrid.rowsData = parsedValidationResult.map((error, index) => ({

src/experiments/ooxml-validator/src/styles/main.scss

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,24 @@ footer {
1414
header {
1515
top: 0;
1616
height: 2.5rem;
17-
background: #185abd;
17+
background: #d83b01;
18+
transition: background 0.5s;
19+
20+
&.docx {
21+
background: #185abd;
22+
}
23+
24+
&.xlsx {
25+
background: #107c41;
26+
}
27+
28+
&.pptx {
29+
background: #c43e1c;
30+
}
1831
}
1932

2033
main {
34+
min-height: 100%;
2135
padding: 2.5rem 0 2.375rem;
2236
}
2337

@@ -51,10 +65,6 @@ h1 {
5165
padding: 0.5rem;
5266
}
5367

54-
fluent-data-grid {
55-
margin-top: 1rem;
56-
}
57-
5868
fluent-data-grid-row.sticky-header {
5969
top: 2.5rem;
6070
}
@@ -66,6 +76,15 @@ fluent-data-grid-cell {
6676
font-variation-settings: initial;
6777
}
6878

79+
#input {
80+
width: 100%;
81+
height: 100%;
82+
display: flex;
83+
flex-direction: column;
84+
align-items: center;
85+
justify-content: center;
86+
}
87+
6988
#output {
7089
width: 100%;
7190
}

0 commit comments

Comments
 (0)