Skip to content

Commit 8ec919b

Browse files
committed
Refactor the RESX reader with better logging.
1 parent 5646f28 commit 8ec919b

1 file changed

Lines changed: 102 additions & 74 deletions

File tree

webpack/localization-plugin/src/utilities/ResxReader.ts

Lines changed: 102 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,25 @@ import { ILocalizedString, ILocFile } from '../interfaces';
1010

1111
const STRING_NAME_RESX: RegExp = /^[A-z_][A-z0-9_]*$/;
1212

13+
export interface ILogger {
14+
logError: (message: string) => void;
15+
logWarning: (message: string) => void;
16+
logFileError: (message: string, filePath: string, line?: number, position?: number) => void;
17+
logFileWarning: (message: string, filePath: string, line?: number, position?: number) => void;
18+
}
19+
20+
export interface IResxReaderOptions extends ILogger {
21+
resxFilePath: string;
22+
}
23+
1324
export class ResxReader {
14-
public static readResxFileAsLocFile(resxFilePath: string): ILocFile {
15-
const fileContents: string = FileSystem.readFile(resxFilePath);
25+
public static readResxFileAsLocFile(options: IResxReaderOptions): ILocFile {
26+
const fileContents: string = FileSystem.readFile(options.resxFilePath);
1627
const xmlDocument: XmlDocument = new XmlDocument(fileContents);
1728

1829
if (xmlDocument.name !== 'root') {
19-
ResxReader._throwResxExceptionWithLocation(
20-
resxFilePath,
30+
ResxReader._logErrorWithLocation(
31+
options,
2132
`Expected RESX to have a "root" element, found "${xmlDocument.name}"`,
2233
xmlDocument
2334
);
@@ -32,22 +43,24 @@ export class ResxReader {
3243
case 'data': {
3344
const stringName: string = childNode.attr.name;
3445
if (!stringName) {
35-
ResxReader._throwResxExceptionWithLocation(
36-
resxFilePath,
46+
ResxReader._logErrorWithLocation(
47+
options,
3748
'Unexpected missing or empty string name',
3849
childNode
3950
);
40-
}
41-
42-
if (!STRING_NAME_RESX.test(stringName)) {
43-
ResxReader._throwResxExceptionWithLocation(
44-
resxFilePath,
51+
} else if (!STRING_NAME_RESX.test(stringName)) {
52+
ResxReader._logErrorWithLocation(
53+
options,
4554
`Invalid string name "${stringName}"`,
4655
childNode
4756
);
48-
}
57+
} else {
58+
const locString: ILocalizedString | undefined = ResxReader._readDataElement(options, childNode);
4959

50-
locFile[stringName] = ResxReader._readDataElement(resxFilePath, childNode);
60+
if (locString) {
61+
locFile[stringName] = locString;
62+
}
63+
}
5164

5265
break;
5366
}
@@ -58,8 +71,8 @@ export class ResxReader {
5871
break;
5972

6073
default:
61-
ResxReader._throwResxExceptionWithLocation(
62-
resxFilePath,
74+
ResxReader._logErrorWithLocation(
75+
options,
6376
`Unexpected RESX element ${childNode.name}`,
6477
childNode
6578
);
@@ -70,172 +83,187 @@ export class ResxReader {
7083

7184
case 'text': {
7285
if (childNode.text.trim() !== '') {
73-
ResxReader._throwResxException(resxFilePath, 'Found unexpected non-empty text node in RESX');
86+
ResxReader._logErrorWithLocation(options, 'Found unexpected non-empty text node in RESX');
7487
}
88+
89+
break;
7590
}
7691

7792
case 'comment':
7893
break;
7994

8095
default:
81-
ResxReader._throwResxException(resxFilePath, `Unexpected ${childNode.type} child in RESX`);
96+
ResxReader._logErrorWithLocation(options, `Unexpected ${childNode.type} child in RESX`);
97+
break;
8298
}
8399
}
84100

85101
return locFile;
86102
}
87103

88-
private static _readDataElement(resxFilePath: string, dataElement: XmlElement): ILocalizedString {
89-
let comment: string | undefined = undefined
104+
private static _readDataElement(options: IResxReaderOptions, dataElement: XmlElement): ILocalizedString | undefined {
105+
let foundCommentElement: boolean = false;
106+
let foundValueElement: boolean = false;
107+
let comment: string | undefined = undefined;
90108
let value: string | undefined = undefined;
91109

92110
for (const childNode of dataElement.children) {
93111
switch (childNode.type) {
94112
case 'element': {
95113
switch (childNode.name) {
96114
case 'value': {
97-
if (value !== undefined) {
98-
ResxReader._throwResxExceptionWithLocation(
99-
resxFilePath,
115+
if (foundValueElement) {
116+
ResxReader._logErrorWithLocation(
117+
options,
100118
'Duplicate <value> element found',
101119
childNode
102120
);
121+
} else {
122+
foundValueElement = true;
123+
value = ResxReader._readTextElement(options, childNode);
103124
}
104125

105-
value = ResxReader._readTextElement(resxFilePath, childNode);
106126
break;
107127
}
108128

109129
case 'comment': {
110-
if (comment !== undefined) {
111-
ResxReader._throwResxExceptionWithLocation(
112-
resxFilePath,
130+
if (foundCommentElement) {
131+
ResxReader._logErrorWithLocation(
132+
options,
113133
'Duplicate <comment> element found',
114134
childNode
115135
);
136+
} else {
137+
foundCommentElement = true;
138+
comment = ResxReader._readTextElement(options, childNode);
116139
}
117140

118-
comment = ResxReader._readTextElement(resxFilePath, childNode);
141+
break;
119142
}
120143

121144
default:
122-
ResxReader._throwResxExceptionWithLocation(
123-
resxFilePath,
145+
ResxReader._logErrorWithLocation(
146+
options,
124147
`Unexpected RESX element ${childNode.name}`,
125148
childNode
126149
);
150+
break;
127151
}
128152

129153
break;
130154
}
131155

132156
case 'text': {
133157
if (childNode.text.trim() !== '') {
134-
ResxReader._throwResxExceptionWithLocation(
135-
resxFilePath,
158+
ResxReader._logErrorWithLocation(
159+
options,
136160
'Found unexpected non-empty text node in RESX <data> element',
137161
dataElement
138162
);
139163
}
164+
165+
break;
140166
}
141167

142168
case 'comment':
143169
break;
144170

145171
default:
146-
ResxReader._throwResxExceptionWithLocation(
147-
resxFilePath,
172+
ResxReader._logErrorWithLocation(
173+
options,
148174
`Unexpected ${childNode.type} child in RESX <data> element`,
149175
dataElement
150176
);
151177
}
152178
}
153179

154180
if (value === undefined) {
155-
ResxReader._throwResxExceptionWithLocation(
156-
resxFilePath,
157-
'Missing <value> element in <data> element',
158-
dataElement
159-
);
160-
} else if (comment === undefined) {
161-
ResxReader._throwResxExceptionWithLocation(
162-
resxFilePath,
163-
'Missing <comment> element in <data> element',
181+
ResxReader._logErrorWithLocation(
182+
options,
183+
'Missing string value in <data> element',
164184
dataElement
165185
);
166186
} else {
187+
if (comment === undefined) {
188+
ResxReader._logWarningWithLocation(
189+
options,
190+
'Missing string comment in <data> element',
191+
dataElement
192+
);
193+
}
194+
167195
return {
168196
value,
169-
comment
197+
comment: comment || ''
170198
};
171199
}
172200
}
173201

174-
private static _readTextElement(resxFilePath: string, element: XmlElement): string {
175-
if (element.children.length !== 1) {
176-
ResxReader._throwResxExceptionWithLocation(
177-
resxFilePath,
178-
'Expected text or CDATA',
179-
element
180-
);
181-
}
182-
202+
private static _readTextElement(options: IResxReaderOptions, element: XmlElement): string | undefined {
183203
let foundText: string | undefined = undefined;
184204

185205
for (const childNode of element.children) {
186206
switch (childNode.type) {
187207
case 'cdata':
188208
case 'text': {
189209
if (foundText !== undefined) {
190-
ResxReader._throwResxExceptionWithLocation(
191-
resxFilePath,
210+
ResxReader._logErrorWithLocation(
211+
options,
192212
'More than one child node found containing text content',
193213
element
194214
);
215+
break;
195216
}
196217

197218
foundText = childNode.type === 'text' ? childNode.text.trim() : childNode.cdata;
219+
break;
198220
}
199221

200222
case 'comment':
201223
break;
202224

203225
case 'element':
204-
ResxReader._throwResxExceptionWithLocation(
205-
resxFilePath,
226+
ResxReader._logErrorWithLocation(
227+
options,
206228
`Unexpected element`,
207229
childNode
208230
);
231+
break;
209232

210233
default:
211-
ResxReader._throwResxExceptionWithLocation(
212-
resxFilePath,
234+
ResxReader._logErrorWithLocation(
235+
options,
213236
`Unexpected ${element.type} child`,
214237
element
215238
);
239+
break;
216240
}
217241
}
218242

219-
if (foundText === undefined) {
220-
ResxReader._throwResxExceptionWithLocation(
221-
resxFilePath,
222-
'Did not find a content node',
223-
element
224-
);
225-
} else {
226-
return foundText;
227-
}
243+
return foundText;
228244
}
229245

230-
private static _throwResxException(resxFilePath: string, message: string): never {
231-
throw new Error(`${resxFilePath}: ${message}`);
246+
private static _logErrorWithLocation(
247+
options: IResxReaderOptions,
248+
message: string,
249+
element?: XmlElement | XmlDocument
250+
): void {
251+
if (element) {
252+
options.logFileError(message, options.resxFilePath, element.line, element.position);
253+
} else {
254+
options.logFileError(message, options.resxFilePath);
255+
}
232256
}
233257

234-
private static _throwResxExceptionWithLocation(
235-
resxFilePath: string,
258+
private static _logWarningWithLocation(
259+
options: IResxReaderOptions,
236260
message: string,
237-
element: XmlElement | XmlDocument
238-
): never {
239-
throw new Error(`${resxFilePath}(${element.line},${element.position}): ${message}`);
261+
element?: XmlElement | XmlDocument
262+
): void {
263+
if (element) {
264+
options.logFileWarning(message, options.resxFilePath, element.line, element.position);
265+
} else {
266+
options.logFileWarning(message, options.resxFilePath);
267+
}
240268
}
241269
}

0 commit comments

Comments
 (0)