diff options
Diffstat (limited to 'src/WixToolset.Core/Localizer.cs')
-rw-r--r-- | src/WixToolset.Core/Localizer.cs | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/src/WixToolset.Core/Localizer.cs b/src/WixToolset.Core/Localizer.cs new file mode 100644 index 00000000..3c299896 --- /dev/null +++ b/src/WixToolset.Core/Localizer.cs | |||
@@ -0,0 +1,384 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Xml.Linq; | ||
8 | using WixToolset.Data; | ||
9 | using WixToolset.Data.Rows; | ||
10 | using WixToolset.Core.Native; | ||
11 | |||
12 | /// <summary> | ||
13 | /// Parses localization files and localizes database values. | ||
14 | /// </summary> | ||
15 | public sealed class Localizer | ||
16 | { | ||
17 | public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl"; | ||
18 | private static string XmlElementName = "WixLocalization"; | ||
19 | |||
20 | private Dictionary<string, WixVariableRow> variables; | ||
21 | private Dictionary<string, LocalizedControl> localizedControls; | ||
22 | |||
23 | /// <summary> | ||
24 | /// Instantiate a new Localizer. | ||
25 | /// </summary> | ||
26 | public Localizer() | ||
27 | { | ||
28 | this.Codepage = -1; | ||
29 | this.variables = new Dictionary<string,WixVariableRow>(); | ||
30 | this.localizedControls = new Dictionary<string, LocalizedControl>(); | ||
31 | } | ||
32 | |||
33 | /// <summary> | ||
34 | /// Gets the codepage. | ||
35 | /// </summary> | ||
36 | /// <value>The codepage.</value> | ||
37 | public int Codepage { get; private set; } | ||
38 | |||
39 | /// <summary> | ||
40 | /// Loads a localization file from a path on disk. | ||
41 | /// </summary> | ||
42 | /// <param name="path">Path to library file saved on disk.</param> | ||
43 | /// <param name="tableDefinitions">Collection containing TableDefinitions to use when loading the localization file.</param> | ||
44 | /// <param name="suppressSchema">Suppress xml schema validation while loading.</param> | ||
45 | /// <returns>Returns the loaded localization file.</returns> | ||
46 | public static Localization ParseLocalizationFile(string path, TableDefinitionCollection tableDefinitions) | ||
47 | { | ||
48 | XElement root = XDocument.Load(path).Root; | ||
49 | Localization localization = null; | ||
50 | |||
51 | SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(root); | ||
52 | if (Localizer.XmlElementName == root.Name.LocalName) | ||
53 | { | ||
54 | if (Localizer.WxlNamespace == root.Name.Namespace) | ||
55 | { | ||
56 | localization = ParseWixLocalizationElement(root, tableDefinitions); | ||
57 | } | ||
58 | else // invalid or missing namespace | ||
59 | { | ||
60 | if (null == root.Name.Namespace) | ||
61 | { | ||
62 | Messaging.Instance.OnMessage(WixErrors.InvalidWixXmlNamespace(sourceLineNumbers, Localizer.XmlElementName, Localizer.WxlNamespace.NamespaceName)); | ||
63 | } | ||
64 | else | ||
65 | { | ||
66 | Messaging.Instance.OnMessage(WixErrors.InvalidWixXmlNamespace(sourceLineNumbers, Localizer.XmlElementName, root.Name.LocalName, Localizer.WxlNamespace.NamespaceName)); | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | else | ||
71 | { | ||
72 | Messaging.Instance.OnMessage(WixErrors.InvalidDocumentElement(sourceLineNumbers, root.Name.LocalName, "localization", Localizer.XmlElementName)); | ||
73 | } | ||
74 | |||
75 | return localization; | ||
76 | } | ||
77 | |||
78 | /// <summary> | ||
79 | /// Add a localization file. | ||
80 | /// </summary> | ||
81 | /// <param name="localization">The localization file to add.</param> | ||
82 | public void AddLocalization(Localization localization) | ||
83 | { | ||
84 | if (-1 == this.Codepage) | ||
85 | { | ||
86 | this.Codepage = localization.Codepage; | ||
87 | } | ||
88 | |||
89 | foreach (WixVariableRow wixVariableRow in localization.Variables) | ||
90 | { | ||
91 | Localizer.AddWixVariable(this.variables, wixVariableRow); | ||
92 | } | ||
93 | |||
94 | foreach (KeyValuePair<string, LocalizedControl> localizedControl in localization.LocalizedControls) | ||
95 | { | ||
96 | if (!this.localizedControls.ContainsKey(localizedControl.Key)) | ||
97 | { | ||
98 | this.localizedControls.Add(localizedControl.Key, localizedControl.Value); | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | |||
103 | /// <summary> | ||
104 | /// Get a localized data value. | ||
105 | /// </summary> | ||
106 | /// <param name="id">The name of the localization variable.</param> | ||
107 | /// <returns>The localized data value or null if it wasn't found.</returns> | ||
108 | public string GetLocalizedValue(string id) | ||
109 | { | ||
110 | WixVariableRow wixVariableRow; | ||
111 | return this.variables.TryGetValue(id, out wixVariableRow) ? wixVariableRow.Value : null; | ||
112 | } | ||
113 | |||
114 | /// <summary> | ||
115 | /// Get a localized control. | ||
116 | /// </summary> | ||
117 | /// <param name="dialog">The optional id of the control's dialog.</param> | ||
118 | /// <param name="control">The id of the control.</param> | ||
119 | /// <returns>The localized control or null if it wasn't found.</returns> | ||
120 | public LocalizedControl GetLocalizedControl(string dialog, string control) | ||
121 | { | ||
122 | LocalizedControl localizedControl; | ||
123 | return this.localizedControls.TryGetValue(LocalizedControl.GetKey(dialog, control), out localizedControl) ? localizedControl : null; | ||
124 | } | ||
125 | |||
126 | /// <summary> | ||
127 | /// Adds a WixVariableRow to a dictionary while performing the expected override checks. | ||
128 | /// </summary> | ||
129 | /// <param name="variables">Dictionary of variable rows.</param> | ||
130 | /// <param name="wixVariableRow">Row to add to the variables dictionary.</param> | ||
131 | private static void AddWixVariable(IDictionary<string, WixVariableRow> variables, WixVariableRow wixVariableRow) | ||
132 | { | ||
133 | WixVariableRow existingWixVariableRow; | ||
134 | if (!variables.TryGetValue(wixVariableRow.Id, out existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) | ||
135 | { | ||
136 | variables[wixVariableRow.Id] = wixVariableRow; | ||
137 | } | ||
138 | else if (!wixVariableRow.Overridable) | ||
139 | { | ||
140 | Messaging.Instance.OnMessage(WixErrors.DuplicateLocalizationIdentifier(wixVariableRow.SourceLineNumbers, wixVariableRow.Id)); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | /// <summary> | ||
145 | /// Parses the WixLocalization element. | ||
146 | /// </summary> | ||
147 | /// <param name="node">Element to parse.</param> | ||
148 | private static Localization ParseWixLocalizationElement(XElement node, TableDefinitionCollection tableDefinitions) | ||
149 | { | ||
150 | int codepage = -1; | ||
151 | string culture = null; | ||
152 | SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); | ||
153 | |||
154 | foreach (XAttribute attrib in node.Attributes()) | ||
155 | { | ||
156 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || Localizer.WxlNamespace == attrib.Name.Namespace) | ||
157 | { | ||
158 | switch (attrib.Name.LocalName) | ||
159 | { | ||
160 | case "Codepage": | ||
161 | codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); | ||
162 | break; | ||
163 | case "Culture": | ||
164 | culture = attrib.Value; | ||
165 | break; | ||
166 | case "Language": | ||
167 | // do nothing; @Language is used for locutil which can't convert Culture to lcid | ||
168 | break; | ||
169 | default: | ||
170 | Common.UnexpectedAttribute(sourceLineNumbers, attrib); | ||
171 | break; | ||
172 | } | ||
173 | } | ||
174 | else | ||
175 | { | ||
176 | Common.UnexpectedAttribute(sourceLineNumbers, attrib); | ||
177 | } | ||
178 | } | ||
179 | |||
180 | Dictionary<string, WixVariableRow> variables = new Dictionary<string,WixVariableRow>(); | ||
181 | Dictionary<string, LocalizedControl> localizedControls = new Dictionary<string, LocalizedControl>(); | ||
182 | |||
183 | foreach (XElement child in node.Elements()) | ||
184 | { | ||
185 | if (Localizer.WxlNamespace == child.Name.Namespace) | ||
186 | { | ||
187 | switch (child.Name.LocalName) | ||
188 | { | ||
189 | case "String": | ||
190 | Localizer.ParseString(child, variables, tableDefinitions); | ||
191 | break; | ||
192 | |||
193 | case "UI": | ||
194 | Localizer.ParseUI(child, localizedControls); | ||
195 | break; | ||
196 | |||
197 | default: | ||
198 | Messaging.Instance.OnMessage(WixErrors.UnexpectedElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); | ||
199 | break; | ||
200 | } | ||
201 | } | ||
202 | else | ||
203 | { | ||
204 | Messaging.Instance.OnMessage(WixErrors.UnsupportedExtensionElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); | ||
205 | } | ||
206 | } | ||
207 | |||
208 | return Messaging.Instance.EncounteredError ? null : new Localization(codepage, culture, variables, localizedControls); | ||
209 | } | ||
210 | |||
211 | /// <summary> | ||
212 | /// Parse a localization string into a WixVariableRow. | ||
213 | /// </summary> | ||
214 | /// <param name="node">Element to parse.</param> | ||
215 | private static void ParseString(XElement node, IDictionary<string, WixVariableRow> variables, TableDefinitionCollection tableDefinitions) | ||
216 | { | ||
217 | string id = null; | ||
218 | bool overridable = false; | ||
219 | SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); | ||
220 | |||
221 | foreach (XAttribute attrib in node.Attributes()) | ||
222 | { | ||
223 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || Localizer.WxlNamespace == attrib.Name.Namespace) | ||
224 | { | ||
225 | switch (attrib.Name.LocalName) | ||
226 | { | ||
227 | case "Id": | ||
228 | id = Common.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
229 | break; | ||
230 | case "Overridable": | ||
231 | overridable = YesNoType.Yes == Common.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
232 | break; | ||
233 | case "Localizable": | ||
234 | ; // do nothing | ||
235 | break; | ||
236 | default: | ||
237 | Messaging.Instance.OnMessage(WixErrors.UnexpectedAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); | ||
238 | break; | ||
239 | } | ||
240 | } | ||
241 | else | ||
242 | { | ||
243 | Messaging.Instance.OnMessage(WixErrors.UnsupportedExtensionAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); | ||
244 | } | ||
245 | } | ||
246 | |||
247 | string value = Common.GetInnerText(node); | ||
248 | |||
249 | if (null == id) | ||
250 | { | ||
251 | Messaging.Instance.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, "String", "Id")); | ||
252 | } | ||
253 | else if (0 == id.Length) | ||
254 | { | ||
255 | Messaging.Instance.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, "String", "Id", 0)); | ||
256 | } | ||
257 | |||
258 | if (!Messaging.Instance.EncounteredError) | ||
259 | { | ||
260 | WixVariableRow wixVariableRow = new WixVariableRow(sourceLineNumbers, tableDefinitions["WixVariable"]); | ||
261 | wixVariableRow.Id = id; | ||
262 | wixVariableRow.Overridable = overridable; | ||
263 | wixVariableRow.Value = value; | ||
264 | |||
265 | Localizer.AddWixVariable(variables, wixVariableRow); | ||
266 | } | ||
267 | } | ||
268 | |||
269 | /// <summary> | ||
270 | /// Parse a localized control. | ||
271 | /// </summary> | ||
272 | /// <param name="node">Element to parse.</param> | ||
273 | /// <param name="localizedControls">Dictionary of localized controls.</param> | ||
274 | private static void ParseUI(XElement node, IDictionary<string, LocalizedControl> localizedControls) | ||
275 | { | ||
276 | string dialog = null; | ||
277 | string control = null; | ||
278 | int x = CompilerConstants.IntegerNotSet; | ||
279 | int y = CompilerConstants.IntegerNotSet; | ||
280 | int width = CompilerConstants.IntegerNotSet; | ||
281 | int height = CompilerConstants.IntegerNotSet; | ||
282 | int attribs = 0; | ||
283 | string text = null; | ||
284 | SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); | ||
285 | |||
286 | foreach (XAttribute attrib in node.Attributes()) | ||
287 | { | ||
288 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || Localizer.WxlNamespace == attrib.Name.Namespace) | ||
289 | { | ||
290 | switch (attrib.Name.LocalName) | ||
291 | { | ||
292 | case "Dialog": | ||
293 | dialog = Common.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
294 | break; | ||
295 | case "Control": | ||
296 | control = Common.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
297 | break; | ||
298 | case "X": | ||
299 | x = Common.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
300 | break; | ||
301 | case "Y": | ||
302 | y = Common.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
303 | break; | ||
304 | case "Width": | ||
305 | width = Common.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
306 | break; | ||
307 | case "Height": | ||
308 | height = Common.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
309 | break; | ||
310 | case "RightToLeft": | ||
311 | if (YesNoType.Yes == Common.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
312 | { | ||
313 | attribs |= MsiInterop.MsidbControlAttributesRTLRO; | ||
314 | } | ||
315 | break; | ||
316 | case "RightAligned": | ||
317 | if (YesNoType.Yes == Common.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
318 | { | ||
319 | attribs |= MsiInterop.MsidbControlAttributesRightAligned; | ||
320 | } | ||
321 | break; | ||
322 | case "LeftScroll": | ||
323 | if (YesNoType.Yes == Common.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
324 | { | ||
325 | attribs |= MsiInterop.MsidbControlAttributesLeftScroll; | ||
326 | } | ||
327 | break; | ||
328 | default: | ||
329 | Common.UnexpectedAttribute(sourceLineNumbers, attrib); | ||
330 | break; | ||
331 | } | ||
332 | } | ||
333 | else | ||
334 | { | ||
335 | Common.UnexpectedAttribute(sourceLineNumbers, attrib); | ||
336 | } | ||
337 | } | ||
338 | |||
339 | text = Common.GetInnerText(node); | ||
340 | |||
341 | if (String.IsNullOrEmpty(control) && 0 < attribs) | ||
342 | { | ||
343 | if (MsiInterop.MsidbControlAttributesRTLRO == (attribs & MsiInterop.MsidbControlAttributesRTLRO)) | ||
344 | { | ||
345 | Messaging.Instance.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightToLeft", "Control")); | ||
346 | } | ||
347 | else if (MsiInterop.MsidbControlAttributesRightAligned == (attribs & MsiInterop.MsidbControlAttributesRightAligned)) | ||
348 | { | ||
349 | Messaging.Instance.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightAligned", "Control")); | ||
350 | } | ||
351 | else if (MsiInterop.MsidbControlAttributesLeftScroll == (attribs & MsiInterop.MsidbControlAttributesLeftScroll)) | ||
352 | { | ||
353 | Messaging.Instance.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "LeftScroll", "Control")); | ||
354 | } | ||
355 | } | ||
356 | |||
357 | if (String.IsNullOrEmpty(control) && String.IsNullOrEmpty(dialog)) | ||
358 | { | ||
359 | Messaging.Instance.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.ToString(), "Dialog", "Control")); | ||
360 | } | ||
361 | |||
362 | if (!Messaging.Instance.EncounteredError) | ||
363 | { | ||
364 | LocalizedControl localizedControl = new LocalizedControl(dialog, control, x, y, width, height, attribs, text); | ||
365 | string key = localizedControl.GetKey(); | ||
366 | if (localizedControls.ContainsKey(key)) | ||
367 | { | ||
368 | if (String.IsNullOrEmpty(localizedControl.Control)) | ||
369 | { | ||
370 | Messaging.Instance.OnMessage(WixErrors.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog)); | ||
371 | } | ||
372 | else | ||
373 | { | ||
374 | Messaging.Instance.OnMessage(WixErrors.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog, localizedControl.Control)); | ||
375 | } | ||
376 | } | ||
377 | else | ||
378 | { | ||
379 | localizedControls.Add(key, localizedControl); | ||
380 | } | ||
381 | } | ||
382 | } | ||
383 | } | ||
384 | } | ||