diff options
Diffstat (limited to 'src/WixToolset.Core/LocalizationParser.cs')
-rw-r--r-- | src/WixToolset.Core/LocalizationParser.cs | 324 |
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 | |||
3 | namespace 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 | } | ||