diff options
Diffstat (limited to 'src/wixext/MsmqCompiler.cs')
-rw-r--r-- | src/wixext/MsmqCompiler.cs | 536 |
1 files changed, 536 insertions, 0 deletions
diff --git a/src/wixext/MsmqCompiler.cs b/src/wixext/MsmqCompiler.cs new file mode 100644 index 00000000..b40e2dc1 --- /dev/null +++ b/src/wixext/MsmqCompiler.cs | |||
@@ -0,0 +1,536 @@ | |||
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.Extensions | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Reflection; | ||
10 | using System.Xml; | ||
11 | using System.Xml.Linq; | ||
12 | using System.Xml.Schema; | ||
13 | using WixToolset.Data; | ||
14 | using WixToolset.Extensibility; | ||
15 | |||
16 | /// <summary> | ||
17 | /// The compiler for the WiX Toolset Internet Information Services Extension. | ||
18 | /// </summary> | ||
19 | public sealed class MsmqCompiler : CompilerExtension | ||
20 | { | ||
21 | /// <summary> | ||
22 | /// Instantiate a new MsmqCompiler. | ||
23 | /// </summary> | ||
24 | public MsmqCompiler() | ||
25 | { | ||
26 | this.Namespace = "http://wixtoolset.org/schemas/v4/wxs/msmq"; | ||
27 | } | ||
28 | |||
29 | /// <summary> | ||
30 | /// </summary> | ||
31 | /// <remarks></remarks> | ||
32 | public enum MqiMessageQueueAttributes | ||
33 | { | ||
34 | Authenticate = (1 << 0), | ||
35 | Journal = (1 << 1), | ||
36 | Transactional = (1 << 2) | ||
37 | } | ||
38 | |||
39 | /// <summary> | ||
40 | /// </summary> | ||
41 | /// <remarks></remarks> | ||
42 | public enum MqiMessageQueuePrivacyLevel | ||
43 | { | ||
44 | None = 0, | ||
45 | Optional = 1, | ||
46 | Body = 2 | ||
47 | } | ||
48 | |||
49 | /// <summary> | ||
50 | /// </summary> | ||
51 | /// <remarks></remarks> | ||
52 | public enum MqiMessageQueuePermission | ||
53 | { | ||
54 | DeleteMessage = (1 << 0), | ||
55 | PeekMessage = (1 << 1), | ||
56 | WriteMessage = (1 << 2), | ||
57 | DeleteJournalMessage = (1 << 3), | ||
58 | SetQueueProperties = (1 << 4), | ||
59 | GetQueueProperties = (1 << 5), | ||
60 | DeleteQueue = (1 << 6), | ||
61 | GetQueuePermissions = (1 << 7), | ||
62 | ChangeQueuePermissions = (1 << 8), | ||
63 | TakeQueueOwnership = (1 << 9), | ||
64 | ReceiveMessage = (1 << 10), | ||
65 | ReceiveJournalMessage = (1 << 11), | ||
66 | QueueGenericRead = (1 << 12), | ||
67 | QueueGenericWrite = (1 << 13), | ||
68 | QueueGenericExecute = (1 << 14), | ||
69 | QueueGenericAll = (1 << 15) | ||
70 | } | ||
71 | |||
72 | /// <summary> | ||
73 | /// Processes an element for the Compiler. | ||
74 | /// </summary> | ||
75 | /// <param name="parentElement">Parent element of element to process.</param> | ||
76 | /// <param name="element">Element to process.</param> | ||
77 | /// <param name="contextValues">Extra information about the context in which this element is being parsed.</param> | ||
78 | public override void ParseElement(XElement parentElement, XElement element, IDictionary<string, string> context) | ||
79 | { | ||
80 | switch (parentElement.Name.LocalName) | ||
81 | { | ||
82 | case "Component": | ||
83 | string componentId = context["ComponentId"]; | ||
84 | string directoryId = context["DirectoryId"]; | ||
85 | |||
86 | switch (element.Name.LocalName) | ||
87 | { | ||
88 | case "MessageQueue": | ||
89 | this.ParseMessageQueueElement(element, componentId); | ||
90 | break; | ||
91 | case "MessageQueuePermission": | ||
92 | this.ParseMessageQueuePermissionElement(element, componentId, null); | ||
93 | break; | ||
94 | default: | ||
95 | this.Core.UnexpectedElement(parentElement, element); | ||
96 | break; | ||
97 | } | ||
98 | break; | ||
99 | default: | ||
100 | this.Core.UnexpectedElement(parentElement, element); | ||
101 | break; | ||
102 | } | ||
103 | } | ||
104 | |||
105 | /// <summary> | ||
106 | /// Parses an MSMQ message queue element. | ||
107 | /// </summary> | ||
108 | /// <param name="node">Element to parse.</param> | ||
109 | /// <param name="componentKey">Identifier of parent component.</param> | ||
110 | private void ParseMessageQueueElement(XElement node, string componentId) | ||
111 | { | ||
112 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
113 | |||
114 | string id = null; | ||
115 | int basePriority = CompilerConstants.IntegerNotSet; | ||
116 | int journalQuota = CompilerConstants.IntegerNotSet; | ||
117 | string label = null; | ||
118 | string multicastAddress = null; | ||
119 | string pathName = null; | ||
120 | int privLevel = CompilerConstants.IntegerNotSet; | ||
121 | int quota = CompilerConstants.IntegerNotSet; | ||
122 | string serviceTypeGuid = null; | ||
123 | int attributes = 0; | ||
124 | |||
125 | foreach (XAttribute attrib in node.Attributes()) | ||
126 | { | ||
127 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) | ||
128 | { | ||
129 | switch (attrib.Name.LocalName) | ||
130 | { | ||
131 | case "Id": | ||
132 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
133 | break; | ||
134 | case "Authenticate": | ||
135 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
136 | { | ||
137 | attributes |= (int)MqiMessageQueueAttributes.Authenticate; | ||
138 | } | ||
139 | else | ||
140 | { | ||
141 | attributes &= ~(int)MqiMessageQueueAttributes.Authenticate; | ||
142 | } | ||
143 | break; | ||
144 | case "BasePriority": | ||
145 | basePriority = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue); | ||
146 | break; | ||
147 | case "Journal": | ||
148 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
149 | { | ||
150 | attributes |= (int)MqiMessageQueueAttributes.Journal; | ||
151 | } | ||
152 | else | ||
153 | { | ||
154 | attributes &= ~(int)MqiMessageQueueAttributes.Journal; | ||
155 | } | ||
156 | break; | ||
157 | case "JournalQuota": | ||
158 | journalQuota = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
159 | break; | ||
160 | case "Label": | ||
161 | label = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
162 | break; | ||
163 | case "MulticastAddress": | ||
164 | multicastAddress = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
165 | break; | ||
166 | case "PathName": | ||
167 | pathName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
168 | break; | ||
169 | case "PrivLevel": | ||
170 | string privLevelAttr = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
171 | switch (privLevelAttr) | ||
172 | { | ||
173 | case "none": | ||
174 | privLevel = (int)MqiMessageQueuePrivacyLevel.None; | ||
175 | break; | ||
176 | case "optional": | ||
177 | privLevel = (int)MqiMessageQueuePrivacyLevel.Optional; | ||
178 | break; | ||
179 | case "body": | ||
180 | privLevel = (int)MqiMessageQueuePrivacyLevel.Body; | ||
181 | break; | ||
182 | default: | ||
183 | this.Core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, "MessageQueue", "PrivLevel", privLevelAttr, "none", "body", "optional")); | ||
184 | break; | ||
185 | } | ||
186 | break; | ||
187 | case "Quota": | ||
188 | quota = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue); | ||
189 | break; | ||
190 | case "Transactional": | ||
191 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
192 | { | ||
193 | attributes |= (int)MqiMessageQueueAttributes.Transactional; | ||
194 | } | ||
195 | else | ||
196 | { | ||
197 | attributes &= ~(int)MqiMessageQueueAttributes.Transactional; | ||
198 | } | ||
199 | break; | ||
200 | case "ServiceTypeGuid": | ||
201 | serviceTypeGuid = TryFormatGuidValue(this.Core.GetAttributeValue(sourceLineNumbers, attrib)); | ||
202 | break; | ||
203 | default: | ||
204 | this.Core.UnexpectedAttribute(node, attrib); | ||
205 | break; | ||
206 | } | ||
207 | } | ||
208 | else | ||
209 | { | ||
210 | this.Core.ParseExtensionAttribute(node, attrib); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | foreach (XElement child in node.Elements()) | ||
215 | { | ||
216 | if (this.Namespace == child.Name.Namespace) | ||
217 | { | ||
218 | switch (child.Name.LocalName) | ||
219 | { | ||
220 | case "MessageQueuePermission": | ||
221 | this.ParseMessageQueuePermissionElement(child, componentId, id); | ||
222 | break; | ||
223 | default: | ||
224 | this.Core.UnexpectedElement(node, child); | ||
225 | break; | ||
226 | } | ||
227 | } | ||
228 | else | ||
229 | { | ||
230 | this.Core.ParseExtensionElement(node, child); | ||
231 | } | ||
232 | } | ||
233 | |||
234 | Row row = this.Core.CreateRow(sourceLineNumbers, "MessageQueue"); | ||
235 | row[0] = id; | ||
236 | row[1] = componentId; | ||
237 | if (CompilerConstants.IntegerNotSet != basePriority) | ||
238 | { | ||
239 | row[2] = basePriority; | ||
240 | } | ||
241 | if (CompilerConstants.IntegerNotSet != journalQuota) | ||
242 | { | ||
243 | row[3] = journalQuota; | ||
244 | } | ||
245 | row[4] = label; | ||
246 | row[5] = multicastAddress; | ||
247 | row[6] = pathName; | ||
248 | if (CompilerConstants.IntegerNotSet != privLevel) | ||
249 | { | ||
250 | row[7] = privLevel; | ||
251 | } | ||
252 | if (CompilerConstants.IntegerNotSet != quota) | ||
253 | { | ||
254 | row[8] = quota; | ||
255 | } | ||
256 | row[9] = serviceTypeGuid; | ||
257 | row[10] = attributes; | ||
258 | |||
259 | this.Core.CreateSimpleReference(sourceLineNumbers, "CustomAction", "MessageQueuingInstall"); | ||
260 | this.Core.CreateSimpleReference(sourceLineNumbers, "CustomAction", "MessageQueuingUninstall"); | ||
261 | } | ||
262 | |||
263 | /// <summary> | ||
264 | /// Parses an MSMQ message queue permission element. | ||
265 | /// </summary> | ||
266 | /// <param name="node">Element to parse.</param> | ||
267 | /// <param name="componentKey">Identifier of parent component.</param> | ||
268 | /// <param name="applicationKey">Optional identifier of parent message queue.</param> | ||
269 | private void ParseMessageQueuePermissionElement(XElement node, string componentId, string messageQueueId) | ||
270 | { | ||
271 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
272 | |||
273 | string id = null; | ||
274 | string user = null; | ||
275 | string group = null; | ||
276 | int permissions = 0; | ||
277 | |||
278 | foreach (XAttribute attrib in node.Attributes()) | ||
279 | { | ||
280 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) | ||
281 | { | ||
282 | switch (attrib.Name.LocalName) | ||
283 | { | ||
284 | case "Id": | ||
285 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
286 | break; | ||
287 | case "MessageQueue": | ||
288 | if (null != messageQueueId) | ||
289 | { | ||
290 | this.Core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); | ||
291 | } | ||
292 | messageQueueId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
293 | this.Core.CreateSimpleReference(sourceLineNumbers, "MessageQueue", messageQueueId); | ||
294 | break; | ||
295 | case "User": | ||
296 | if (null != group) | ||
297 | { | ||
298 | this.Core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "User", "Group")); | ||
299 | } | ||
300 | user = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
301 | this.Core.CreateSimpleReference(sourceLineNumbers, "User", user); | ||
302 | break; | ||
303 | case "Group": | ||
304 | if (null != user) | ||
305 | { | ||
306 | this.Core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Group", "User")); | ||
307 | } | ||
308 | group = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
309 | this.Core.CreateSimpleReference(sourceLineNumbers, "Group", group); | ||
310 | break; | ||
311 | case "DeleteMessage": | ||
312 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
313 | { | ||
314 | permissions |= (int)MqiMessageQueuePermission.DeleteMessage; | ||
315 | } | ||
316 | else | ||
317 | { | ||
318 | permissions &= ~(int)MqiMessageQueuePermission.DeleteMessage; | ||
319 | } | ||
320 | break; | ||
321 | case "PeekMessage": | ||
322 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
323 | { | ||
324 | permissions |= (int)MqiMessageQueuePermission.PeekMessage; | ||
325 | } | ||
326 | else | ||
327 | { | ||
328 | permissions &= ~(int)MqiMessageQueuePermission.PeekMessage; | ||
329 | } | ||
330 | break; | ||
331 | case "WriteMessage": | ||
332 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
333 | { | ||
334 | permissions |= (int)MqiMessageQueuePermission.WriteMessage; | ||
335 | } | ||
336 | else | ||
337 | { | ||
338 | permissions &= ~(int)MqiMessageQueuePermission.WriteMessage; | ||
339 | } | ||
340 | break; | ||
341 | case "DeleteJournalMessage": | ||
342 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
343 | { | ||
344 | permissions |= (int)MqiMessageQueuePermission.DeleteJournalMessage; | ||
345 | } | ||
346 | else | ||
347 | { | ||
348 | permissions &= ~(int)MqiMessageQueuePermission.DeleteJournalMessage; | ||
349 | } | ||
350 | break; | ||
351 | case "SetQueueProperties": | ||
352 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
353 | { | ||
354 | permissions |= (int)MqiMessageQueuePermission.SetQueueProperties; | ||
355 | } | ||
356 | else | ||
357 | { | ||
358 | permissions &= ~(int)MqiMessageQueuePermission.SetQueueProperties; | ||
359 | } | ||
360 | break; | ||
361 | case "GetQueueProperties": | ||
362 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
363 | { | ||
364 | permissions |= (int)MqiMessageQueuePermission.GetQueueProperties; | ||
365 | } | ||
366 | else | ||
367 | { | ||
368 | permissions &= ~(int)MqiMessageQueuePermission.GetQueueProperties; | ||
369 | } | ||
370 | break; | ||
371 | case "DeleteQueue": | ||
372 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
373 | { | ||
374 | permissions |= (int)MqiMessageQueuePermission.DeleteQueue; | ||
375 | } | ||
376 | else | ||
377 | { | ||
378 | permissions &= ~(int)MqiMessageQueuePermission.DeleteQueue; | ||
379 | } | ||
380 | break; | ||
381 | case "GetQueuePermissions": | ||
382 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
383 | { | ||
384 | permissions |= (int)MqiMessageQueuePermission.GetQueuePermissions; | ||
385 | } | ||
386 | else | ||
387 | { | ||
388 | permissions &= ~(int)MqiMessageQueuePermission.GetQueuePermissions; | ||
389 | } | ||
390 | break; | ||
391 | case "ChangeQueuePermissions": | ||
392 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
393 | { | ||
394 | permissions |= (int)MqiMessageQueuePermission.ChangeQueuePermissions; | ||
395 | } | ||
396 | else | ||
397 | { | ||
398 | permissions &= ~(int)MqiMessageQueuePermission.ChangeQueuePermissions; | ||
399 | } | ||
400 | break; | ||
401 | case "TakeQueueOwnership": | ||
402 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
403 | { | ||
404 | permissions |= (int)MqiMessageQueuePermission.TakeQueueOwnership; | ||
405 | } | ||
406 | else | ||
407 | { | ||
408 | permissions &= ~(int)MqiMessageQueuePermission.TakeQueueOwnership; | ||
409 | } | ||
410 | break; | ||
411 | case "ReceiveMessage": | ||
412 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
413 | { | ||
414 | permissions |= (int)MqiMessageQueuePermission.ReceiveMessage; | ||
415 | } | ||
416 | else | ||
417 | { | ||
418 | permissions &= ~(int)MqiMessageQueuePermission.ReceiveMessage; | ||
419 | } | ||
420 | break; | ||
421 | case "ReceiveJournalMessage": | ||
422 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
423 | { | ||
424 | permissions |= (int)MqiMessageQueuePermission.ReceiveJournalMessage; | ||
425 | } | ||
426 | else | ||
427 | { | ||
428 | permissions &= ~(int)MqiMessageQueuePermission.ReceiveJournalMessage; | ||
429 | } | ||
430 | break; | ||
431 | case "QueueGenericRead": | ||
432 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
433 | { | ||
434 | permissions |= (int)MqiMessageQueuePermission.QueueGenericRead; | ||
435 | } | ||
436 | else | ||
437 | { | ||
438 | permissions &= ~(int)MqiMessageQueuePermission.QueueGenericRead; | ||
439 | } | ||
440 | break; | ||
441 | case "QueueGenericWrite": | ||
442 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
443 | { | ||
444 | permissions |= (int)MqiMessageQueuePermission.QueueGenericWrite; | ||
445 | } | ||
446 | else | ||
447 | { | ||
448 | permissions &= ~(int)MqiMessageQueuePermission.QueueGenericWrite; | ||
449 | } | ||
450 | break; | ||
451 | case "QueueGenericExecute": | ||
452 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
453 | { | ||
454 | permissions |= (int)MqiMessageQueuePermission.QueueGenericExecute; | ||
455 | } | ||
456 | else | ||
457 | { | ||
458 | permissions &= ~(int)MqiMessageQueuePermission.QueueGenericExecute; | ||
459 | } | ||
460 | break; | ||
461 | case "QueueGenericAll": | ||
462 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
463 | { | ||
464 | permissions |= (int)MqiMessageQueuePermission.QueueGenericAll; | ||
465 | } | ||
466 | else | ||
467 | { | ||
468 | permissions &= ~(int)MqiMessageQueuePermission.QueueGenericAll; | ||
469 | } | ||
470 | break; | ||
471 | default: | ||
472 | this.Core.UnexpectedAttribute(node, attrib); | ||
473 | break; | ||
474 | } | ||
475 | } | ||
476 | else | ||
477 | { | ||
478 | this.Core.ParseExtensionAttribute(node, attrib); | ||
479 | } | ||
480 | } | ||
481 | |||
482 | if (null == messageQueueId) | ||
483 | { | ||
484 | this.Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MessageQueue")); | ||
485 | } | ||
486 | if (null == user && null == group) | ||
487 | { | ||
488 | this.Core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "User", "Group")); | ||
489 | } | ||
490 | |||
491 | this.Core.ParseForExtensionElements(node); | ||
492 | |||
493 | if (null != user) | ||
494 | { | ||
495 | Row row = this.Core.CreateRow(sourceLineNumbers, "MessageQueueUserPermission"); | ||
496 | row[0] = id; | ||
497 | row[1] = componentId; | ||
498 | row[2] = messageQueueId; | ||
499 | row[3] = user; | ||
500 | row[4] = permissions; | ||
501 | } | ||
502 | if (null != group) | ||
503 | { | ||
504 | Row row = this.Core.CreateRow(sourceLineNumbers, "MessageQueueGroupPermission"); | ||
505 | row[0] = id; | ||
506 | row[1] = componentId; | ||
507 | row[2] = messageQueueId; | ||
508 | row[3] = group; | ||
509 | row[4] = permissions; | ||
510 | } | ||
511 | } | ||
512 | |||
513 | /// <summary> | ||
514 | /// Attempts to parse the input value as a GUID, and in case the value is a valid | ||
515 | /// GUID returnes it in the format "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}". | ||
516 | /// </summary> | ||
517 | /// <param name="val"></param> | ||
518 | /// <returns></returns> | ||
519 | string TryFormatGuidValue(string val) | ||
520 | { | ||
521 | try | ||
522 | { | ||
523 | Guid guid = new Guid(val); | ||
524 | return guid.ToString("B").ToUpper(); | ||
525 | } | ||
526 | catch (FormatException) | ||
527 | { | ||
528 | return val; | ||
529 | } | ||
530 | catch (OverflowException) | ||
531 | { | ||
532 | return val; | ||
533 | } | ||
534 | } | ||
535 | } | ||
536 | } | ||