aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/LocalizationParser.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/LocalizationParser.cs')
-rw-r--r--src/WixToolset.Core/LocalizationParser.cs324
1 files changed, 324 insertions, 0 deletions
diff --git a/src/WixToolset.Core/LocalizationParser.cs b/src/WixToolset.Core/LocalizationParser.cs
new file mode 100644
index 00000000..f7f86a54
--- /dev/null
+++ b/src/WixToolset.Core/LocalizationParser.cs
@@ -0,0 +1,324 @@
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.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Xml.Linq;
8 using WixToolset.Core.Native;
9 using WixToolset.Data;
10 using WixToolset.Data.Bind;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Services;
13
14 internal class LocalizationParser : ILocalizationParser
15 {
16 public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl";
17 private static string XmlElementName = "WixLocalization";
18
19 internal LocalizationParser(IServiceProvider serviceProvider)
20 {
21 this.Messaging = serviceProvider.GetService<IMessaging>();
22 }
23
24 private IMessaging Messaging { get; }
25
26 public Localization ParseLocalization(string path)
27 {
28 var document = XDocument.Load(path);
29 return this.ParseLocalization(document);
30 }
31
32 public Localization ParseLocalization(XDocument document)
33 {
34 XElement root = document.Root;
35 Localization localization = null;
36
37 SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(root);
38 if (LocalizationParser.XmlElementName == root.Name.LocalName)
39 {
40 if (LocalizationParser.WxlNamespace == root.Name.Namespace)
41 {
42 localization = ParseWixLocalizationElement(this.Messaging, root);
43 }
44 else // invalid or missing namespace
45 {
46 if (null == root.Name.Namespace)
47 {
48 this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, LocalizationParser.WxlNamespace.NamespaceName));
49 }
50 else
51 {
52 this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, root.Name.LocalName, LocalizationParser.WxlNamespace.NamespaceName));
53 }
54 }
55 }
56 else
57 {
58 this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, root.Name.LocalName, "localization", LocalizationParser.XmlElementName));
59 }
60
61 return localization;
62 }
63
64 /// <summary>
65 /// Adds a WixVariableRow to a dictionary while performing the expected override checks.
66 /// </summary>
67 /// <param name="variables">Dictionary of variable rows.</param>
68 /// <param name="wixVariableRow">Row to add to the variables dictionary.</param>
69 private static void AddWixVariable(IMessaging messaging, IDictionary<string, BindVariable> variables, BindVariable wixVariableRow)
70 {
71 if (!variables.TryGetValue(wixVariableRow.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable))
72 {
73 variables[wixVariableRow.Id] = wixVariableRow;
74 }
75 else if (!wixVariableRow.Overridable)
76 {
77 messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(wixVariableRow.SourceLineNumbers, wixVariableRow.Id));
78 }
79 }
80
81 /// <summary>
82 /// Parses the WixLocalization element.
83 /// </summary>
84 /// <param name="node">Element to parse.</param>
85 private static Localization ParseWixLocalizationElement(IMessaging messaging, XElement node)
86 {
87 int codepage = -1;
88 string culture = null;
89 SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(node);
90
91 foreach (XAttribute attrib in node.Attributes())
92 {
93 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace)
94 {
95 switch (attrib.Name.LocalName)
96 {
97 case "Codepage":
98 codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers);
99 break;
100 case "Culture":
101 culture = attrib.Value;
102 break;
103 case "Language":
104 // do nothing; @Language is used for locutil which can't convert Culture to lcid
105 break;
106 default:
107 Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib);
108 break;
109 }
110 }
111 else
112 {
113 Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib);
114 }
115 }
116
117 Dictionary<string, BindVariable> variables = new Dictionary<string, BindVariable>();
118 Dictionary<string, LocalizedControl> localizedControls = new Dictionary<string, LocalizedControl>();
119
120 foreach (XElement child in node.Elements())
121 {
122 if (LocalizationParser.WxlNamespace == child.Name.Namespace)
123 {
124 switch (child.Name.LocalName)
125 {
126 case "String":
127 LocalizationParser.ParseString(messaging, child, variables);
128 break;
129
130 case "UI":
131 LocalizationParser.ParseUI(messaging, child, localizedControls);
132 break;
133
134 default:
135 messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString()));
136 break;
137 }
138 }
139 else
140 {
141 messaging.Write(ErrorMessages.UnsupportedExtensionElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString()));
142 }
143 }
144
145 return messaging.EncounteredError ? null : new Localization(codepage, culture, variables, localizedControls);
146 }
147
148 /// <summary>
149 /// Parse a localization string into a WixVariableRow.
150 /// </summary>
151 /// <param name="node">Element to parse.</param>
152 private static void ParseString(IMessaging messaging, XElement node, IDictionary<string, BindVariable> variables)
153 {
154 string id = null;
155 bool overridable = false;
156 SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(node);
157
158 foreach (XAttribute attrib in node.Attributes())
159 {
160 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace)
161 {
162 switch (attrib.Name.LocalName)
163 {
164 case "Id":
165 id = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib);
166 break;
167 case "Overridable":
168 overridable = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib);
169 break;
170 case "Localizable":
171 ; // do nothing
172 break;
173 default:
174 messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString()));
175 break;
176 }
177 }
178 else
179 {
180 messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString()));
181 }
182 }
183
184 string value = Common.GetInnerText(node);
185
186 if (null == id)
187 {
188 messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "String", "Id"));
189 }
190 else if (0 == id.Length)
191 {
192 messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, "String", "Id", 0));
193 }
194
195 if (!messaging.EncounteredError)
196 {
197 var variable = new BindVariable
198 {
199 SourceLineNumbers = sourceLineNumbers,
200 Id = id,
201 Overridable = overridable,
202 Value = value,
203 };
204
205 LocalizationParser.AddWixVariable(messaging, variables, variable);
206 }
207 }
208
209 /// <summary>
210 /// Parse a localized control.
211 /// </summary>
212 /// <param name="node">Element to parse.</param>
213 /// <param name="localizedControls">Dictionary of localized controls.</param>
214 private static void ParseUI(IMessaging messaging, XElement node, IDictionary<string, LocalizedControl> localizedControls)
215 {
216 string dialog = null;
217 string control = null;
218 int x = CompilerConstants.IntegerNotSet;
219 int y = CompilerConstants.IntegerNotSet;
220 int width = CompilerConstants.IntegerNotSet;
221 int height = CompilerConstants.IntegerNotSet;
222 int attribs = 0;
223 string text = null;
224 SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(node);
225
226 foreach (XAttribute attrib in node.Attributes())
227 {
228 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace)
229 {
230 switch (attrib.Name.LocalName)
231 {
232 case "Dialog":
233 dialog = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib);
234 break;
235 case "Control":
236 control = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib);
237 break;
238 case "X":
239 x = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, short.MaxValue);
240 break;
241 case "Y":
242 y = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, short.MaxValue);
243 break;
244 case "Width":
245 width = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, short.MaxValue);
246 break;
247 case "Height":
248 height = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, short.MaxValue);
249 break;
250 case "RightToLeft":
251 if (YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib))
252 {
253 attribs |= MsiInterop.MsidbControlAttributesRTLRO;
254 }
255 break;
256 case "RightAligned":
257 if (YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib))
258 {
259 attribs |= MsiInterop.MsidbControlAttributesRightAligned;
260 }
261 break;
262 case "LeftScroll":
263 if (YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib))
264 {
265 attribs |= MsiInterop.MsidbControlAttributesLeftScroll;
266 }
267 break;
268 default:
269 Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib);
270 break;
271 }
272 }
273 else
274 {
275 Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib);
276 }
277 }
278
279 text = Common.GetInnerText(node);
280
281 if (String.IsNullOrEmpty(control) && 0 < attribs)
282 {
283 if (MsiInterop.MsidbControlAttributesRTLRO == (attribs & MsiInterop.MsidbControlAttributesRTLRO))
284 {
285 messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightToLeft", "Control"));
286 }
287 else if (MsiInterop.MsidbControlAttributesRightAligned == (attribs & MsiInterop.MsidbControlAttributesRightAligned))
288 {
289 messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightAligned", "Control"));
290 }
291 else if (MsiInterop.MsidbControlAttributesLeftScroll == (attribs & MsiInterop.MsidbControlAttributesLeftScroll))
292 {
293 messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "LeftScroll", "Control"));
294 }
295 }
296
297 if (String.IsNullOrEmpty(control) && String.IsNullOrEmpty(dialog))
298 {
299 messaging.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.ToString(), "Dialog", "Control"));
300 }
301
302 if (!messaging.EncounteredError)
303 {
304 LocalizedControl localizedControl = new LocalizedControl(dialog, control, x, y, width, height, attribs, text);
305 string key = localizedControl.GetKey();
306 if (localizedControls.ContainsKey(key))
307 {
308 if (String.IsNullOrEmpty(localizedControl.Control))
309 {
310 messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog));
311 }
312 else
313 {
314 messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog, localizedControl.Control));
315 }
316 }
317 else
318 {
319 localizedControls.Add(key, localizedControl);
320 }
321 }
322 }
323 }
324}