aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Localizer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/Localizer.cs')
-rw-r--r--src/WixToolset.Core/Localizer.cs384
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
3namespace 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}