diff options
Diffstat (limited to 'src/wixext/SqlDecompiler.cs')
-rw-r--r-- | src/wixext/SqlDecompiler.cs | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/src/wixext/SqlDecompiler.cs b/src/wixext/SqlDecompiler.cs new file mode 100644 index 00000000..64192e84 --- /dev/null +++ b/src/wixext/SqlDecompiler.cs | |||
@@ -0,0 +1,512 @@ | |||
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.Collections; | ||
6 | using WixToolset.Data; | ||
7 | using WixToolset.Extensibility; | ||
8 | using Sql = WixToolset.Extensions.Serialize.Sql; | ||
9 | using Wix = WixToolset.Data.Serialize; | ||
10 | |||
11 | /// <summary> | ||
12 | /// The decompiler for the WiX Toolset SQL Server Extension. | ||
13 | /// </summary> | ||
14 | public sealed class SqlDecompiler : DecompilerExtension | ||
15 | { | ||
16 | /// <summary> | ||
17 | /// Creates a decompiler for SQL Extension. | ||
18 | /// </summary> | ||
19 | public SqlDecompiler() | ||
20 | { | ||
21 | this.TableDefinitions = SqlExtensionData.GetExtensionTableDefinitions(); | ||
22 | } | ||
23 | |||
24 | /// <summary> | ||
25 | /// Get the extensions library to be removed. | ||
26 | /// </summary> | ||
27 | /// <param name="tableDefinitions">Table definitions for library.</param> | ||
28 | /// <returns>Library to remove from decompiled output.</returns> | ||
29 | public override Library GetLibraryToRemove(TableDefinitionCollection tableDefinitions) | ||
30 | { | ||
31 | return SqlExtensionData.GetExtensionLibrary(tableDefinitions); | ||
32 | } | ||
33 | |||
34 | /// <summary> | ||
35 | /// Decompiles an extension table. | ||
36 | /// </summary> | ||
37 | /// <param name="table">The table to decompile.</param> | ||
38 | public override void DecompileTable(Table table) | ||
39 | { | ||
40 | switch (table.Name) | ||
41 | { | ||
42 | case "SqlDatabase": | ||
43 | this.DecompileSqlDatabaseTable(table); | ||
44 | break; | ||
45 | case "SqlFileSpec": | ||
46 | // handled in FinalizeSqlFileSpecTable | ||
47 | break; | ||
48 | case "SqlScript": | ||
49 | this.DecompileSqlScriptTable(table); | ||
50 | break; | ||
51 | case "SqlString": | ||
52 | this.DecompileSqlStringTable(table); | ||
53 | break; | ||
54 | default: | ||
55 | base.DecompileTable(table); | ||
56 | break; | ||
57 | } | ||
58 | } | ||
59 | |||
60 | /// <summary> | ||
61 | /// Finalize decompilation. | ||
62 | /// </summary> | ||
63 | /// <param name="tables">The collection of all tables.</param> | ||
64 | public override void Finish(TableIndexedCollection tables) | ||
65 | { | ||
66 | this.FinalizeSqlFileSpecTable(tables); | ||
67 | this.FinalizeSqlScriptAndSqlStringTables(tables); | ||
68 | } | ||
69 | |||
70 | /// <summary> | ||
71 | /// Decompile the SqlDatabase table. | ||
72 | /// </summary> | ||
73 | /// <param name="table">The table to decompile.</param> | ||
74 | private void DecompileSqlDatabaseTable(Table table) | ||
75 | { | ||
76 | foreach (Row row in table.Rows) | ||
77 | { | ||
78 | Sql.SqlDatabase sqlDatabase = new Sql.SqlDatabase(); | ||
79 | |||
80 | sqlDatabase.Id = (string)row[0]; | ||
81 | |||
82 | if (null != row[1]) | ||
83 | { | ||
84 | sqlDatabase.Server = (string)row[1]; | ||
85 | } | ||
86 | |||
87 | if (null != row[2]) | ||
88 | { | ||
89 | sqlDatabase.Instance = (string)row[2]; | ||
90 | } | ||
91 | |||
92 | sqlDatabase.Database = (string)row[3]; | ||
93 | |||
94 | if (null != row[5]) | ||
95 | { | ||
96 | sqlDatabase.User = (string)row[5]; | ||
97 | } | ||
98 | |||
99 | // the FileSpec_ and FileSpec_Log columns will be handled in FinalizeSqlFileSpecTable | ||
100 | |||
101 | if (null != row[8]) | ||
102 | { | ||
103 | int attributes = (int)row[8]; | ||
104 | |||
105 | if (SqlCompiler.DbCreateOnInstall == (attributes & SqlCompiler.DbCreateOnInstall)) | ||
106 | { | ||
107 | sqlDatabase.CreateOnInstall = Sql.YesNoType.yes; | ||
108 | } | ||
109 | |||
110 | if (SqlCompiler.DbDropOnUninstall == (attributes & SqlCompiler.DbDropOnUninstall)) | ||
111 | { | ||
112 | sqlDatabase.DropOnUninstall = Sql.YesNoType.yes; | ||
113 | } | ||
114 | |||
115 | if (SqlCompiler.DbContinueOnError == (attributes & SqlCompiler.DbContinueOnError)) | ||
116 | { | ||
117 | sqlDatabase.ContinueOnError = Sql.YesNoType.yes; | ||
118 | } | ||
119 | |||
120 | if (SqlCompiler.DbDropOnInstall == (attributes & SqlCompiler.DbDropOnInstall)) | ||
121 | { | ||
122 | sqlDatabase.DropOnInstall = Sql.YesNoType.yes; | ||
123 | } | ||
124 | |||
125 | if (SqlCompiler.DbCreateOnUninstall == (attributes & SqlCompiler.DbCreateOnUninstall)) | ||
126 | { | ||
127 | sqlDatabase.CreateOnUninstall = Sql.YesNoType.yes; | ||
128 | } | ||
129 | |||
130 | if (SqlCompiler.DbConfirmOverwrite == (attributes & SqlCompiler.DbConfirmOverwrite)) | ||
131 | { | ||
132 | sqlDatabase.ConfirmOverwrite = Sql.YesNoType.yes; | ||
133 | } | ||
134 | |||
135 | if (SqlCompiler.DbCreateOnReinstall == (attributes & SqlCompiler.DbCreateOnReinstall)) | ||
136 | { | ||
137 | sqlDatabase.CreateOnReinstall = Sql.YesNoType.yes; | ||
138 | } | ||
139 | |||
140 | if (SqlCompiler.DbDropOnReinstall == (attributes & SqlCompiler.DbDropOnReinstall)) | ||
141 | { | ||
142 | sqlDatabase.DropOnReinstall = Sql.YesNoType.yes; | ||
143 | } | ||
144 | } | ||
145 | |||
146 | if (null != row[4]) | ||
147 | { | ||
148 | Wix.Component component = (Wix.Component)this.Core.GetIndexedElement("Component", (string)row[4]); | ||
149 | |||
150 | if (null != component) | ||
151 | { | ||
152 | component.AddChild(sqlDatabase); | ||
153 | } | ||
154 | else | ||
155 | { | ||
156 | this.Core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", (string)row[4], "Component")); | ||
157 | } | ||
158 | } | ||
159 | else | ||
160 | { | ||
161 | this.Core.RootElement.AddChild(sqlDatabase); | ||
162 | } | ||
163 | this.Core.IndexElement(row, sqlDatabase); | ||
164 | } | ||
165 | } | ||
166 | |||
167 | /// <summary> | ||
168 | /// Decompile the SqlScript table. | ||
169 | /// </summary> | ||
170 | /// <param name="table">The table to decompile.</param> | ||
171 | private void DecompileSqlScriptTable(Table table) | ||
172 | { | ||
173 | foreach (Row row in table.Rows) | ||
174 | { | ||
175 | Sql.SqlScript sqlScript = new Sql.SqlScript(); | ||
176 | |||
177 | sqlScript.Id = (string)row[0]; | ||
178 | |||
179 | // the Db_ and Component_ columns are handled in FinalizeSqlScriptAndSqlStringTables | ||
180 | |||
181 | sqlScript.BinaryKey = (string)row[3]; | ||
182 | |||
183 | if (null != row[4]) | ||
184 | { | ||
185 | sqlScript.User = (string)row[4]; | ||
186 | } | ||
187 | |||
188 | int attributes = (int)row[5]; | ||
189 | |||
190 | if (SqlCompiler.SqlContinueOnError == (attributes & SqlCompiler.SqlContinueOnError)) | ||
191 | { | ||
192 | sqlScript.ContinueOnError = Sql.YesNoType.yes; | ||
193 | } | ||
194 | |||
195 | if (SqlCompiler.SqlExecuteOnInstall == (attributes & SqlCompiler.SqlExecuteOnInstall)) | ||
196 | { | ||
197 | sqlScript.ExecuteOnInstall = Sql.YesNoType.yes; | ||
198 | } | ||
199 | |||
200 | if (SqlCompiler.SqlExecuteOnReinstall == (attributes & SqlCompiler.SqlExecuteOnReinstall)) | ||
201 | { | ||
202 | sqlScript.ExecuteOnReinstall = Sql.YesNoType.yes; | ||
203 | } | ||
204 | |||
205 | if (SqlCompiler.SqlExecuteOnUninstall == (attributes & SqlCompiler.SqlExecuteOnUninstall)) | ||
206 | { | ||
207 | sqlScript.ExecuteOnUninstall = Sql.YesNoType.yes; | ||
208 | } | ||
209 | |||
210 | if ((SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnInstall) == (attributes & (SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnInstall))) | ||
211 | { | ||
212 | sqlScript.RollbackOnInstall = Sql.YesNoType.yes; | ||
213 | } | ||
214 | |||
215 | if ((SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnReinstall) == (attributes & (SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnReinstall))) | ||
216 | { | ||
217 | sqlScript.RollbackOnReinstall = Sql.YesNoType.yes; | ||
218 | } | ||
219 | |||
220 | if ((SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnUninstall) == (attributes & (SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnUninstall))) | ||
221 | { | ||
222 | sqlScript.RollbackOnUninstall = Sql.YesNoType.yes; | ||
223 | } | ||
224 | |||
225 | if (null != row[6]) | ||
226 | { | ||
227 | sqlScript.Sequence = (int)row[6]; | ||
228 | } | ||
229 | |||
230 | this.Core.IndexElement(row, sqlScript); | ||
231 | } | ||
232 | } | ||
233 | |||
234 | /// <summary> | ||
235 | /// Decompile the SqlString table. | ||
236 | /// </summary> | ||
237 | /// <param name="table">The table to decompile.</param> | ||
238 | private void DecompileSqlStringTable(Table table) | ||
239 | { | ||
240 | foreach (Row row in table.Rows) | ||
241 | { | ||
242 | Sql.SqlString sqlString = new Sql.SqlString(); | ||
243 | |||
244 | sqlString.Id = (string)row[0]; | ||
245 | |||
246 | // the Db_ and Component_ columns are handled in FinalizeSqlScriptAndSqlStringTables | ||
247 | |||
248 | sqlString.SQL = (string)row[3]; | ||
249 | |||
250 | if (null != row[4]) | ||
251 | { | ||
252 | sqlString.User = (string)row[4]; | ||
253 | } | ||
254 | |||
255 | int attributes = (int)row[5]; | ||
256 | |||
257 | if (SqlCompiler.SqlContinueOnError == (attributes & SqlCompiler.SqlContinueOnError)) | ||
258 | { | ||
259 | sqlString.ContinueOnError = Sql.YesNoType.yes; | ||
260 | } | ||
261 | |||
262 | if (SqlCompiler.SqlExecuteOnInstall == (attributes & SqlCompiler.SqlExecuteOnInstall)) | ||
263 | { | ||
264 | sqlString.ExecuteOnInstall = Sql.YesNoType.yes; | ||
265 | } | ||
266 | |||
267 | if (SqlCompiler.SqlExecuteOnReinstall == (attributes & SqlCompiler.SqlExecuteOnReinstall)) | ||
268 | { | ||
269 | sqlString.ExecuteOnReinstall = Sql.YesNoType.yes; | ||
270 | } | ||
271 | |||
272 | if (SqlCompiler.SqlExecuteOnUninstall == (attributes & SqlCompiler.SqlExecuteOnUninstall)) | ||
273 | { | ||
274 | sqlString.ExecuteOnUninstall = Sql.YesNoType.yes; | ||
275 | } | ||
276 | |||
277 | if ((SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnInstall) == (attributes & (SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnInstall))) | ||
278 | { | ||
279 | sqlString.RollbackOnInstall = Sql.YesNoType.yes; | ||
280 | } | ||
281 | |||
282 | if ((SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnReinstall) == (attributes & (SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnReinstall))) | ||
283 | { | ||
284 | sqlString.RollbackOnReinstall = Sql.YesNoType.yes; | ||
285 | } | ||
286 | |||
287 | if ((SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnUninstall) == (attributes & (SqlCompiler.SqlRollback | SqlCompiler.SqlExecuteOnUninstall))) | ||
288 | { | ||
289 | sqlString.RollbackOnUninstall = Sql.YesNoType.yes; | ||
290 | } | ||
291 | |||
292 | if (null != row[6]) | ||
293 | { | ||
294 | sqlString.Sequence = (int)row[6]; | ||
295 | } | ||
296 | |||
297 | this.Core.IndexElement(row, sqlString); | ||
298 | } | ||
299 | } | ||
300 | |||
301 | /// <summary> | ||
302 | /// Finalize the SqlFileSpec table. | ||
303 | /// </summary> | ||
304 | /// <param name="tables">The collection of all tables.</param> | ||
305 | /// <remarks> | ||
306 | /// Since rows of the SqlFileSpec table are represented by either | ||
307 | /// the SqlFileSpec or SqlLogFileSpec depending upon the context in | ||
308 | /// which they are used in the SqlDatabase table, decompilation of this | ||
309 | /// table must occur after the SqlDatbase parents are decompiled. | ||
310 | /// </remarks> | ||
311 | private void FinalizeSqlFileSpecTable(TableIndexedCollection tables) | ||
312 | { | ||
313 | Table sqlDatabaseTable = tables["SqlDatabase"]; | ||
314 | Table sqlFileSpecTable = tables["SqlFileSpec"]; | ||
315 | |||
316 | if (null != sqlDatabaseTable && null != sqlFileSpecTable) | ||
317 | { | ||
318 | Hashtable sqlFileSpecRows = new Hashtable(); | ||
319 | |||
320 | // index each SqlFileSpec row by its primary key | ||
321 | foreach (Row row in sqlFileSpecTable.Rows) | ||
322 | { | ||
323 | sqlFileSpecRows.Add(row[0], row); | ||
324 | } | ||
325 | |||
326 | // create the necessary SqlFileSpec and SqlLogFileSpec elements for each row | ||
327 | foreach (Row row in sqlDatabaseTable.Rows) | ||
328 | { | ||
329 | Sql.SqlDatabase sqlDatabase = (Sql.SqlDatabase)this.Core.GetIndexedElement(row); | ||
330 | |||
331 | if (null != row[6]) | ||
332 | { | ||
333 | Row sqlFileSpecRow = (Row)sqlFileSpecRows[row[6]]; | ||
334 | |||
335 | if (null != sqlFileSpecRow) | ||
336 | { | ||
337 | Sql.SqlFileSpec sqlFileSpec = new Sql.SqlFileSpec(); | ||
338 | |||
339 | sqlFileSpec.Id = (string)sqlFileSpecRow[0]; | ||
340 | |||
341 | if (null != sqlFileSpecRow[1]) | ||
342 | { | ||
343 | sqlFileSpec.Name = (string)sqlFileSpecRow[1]; | ||
344 | } | ||
345 | |||
346 | sqlFileSpec.Filename = (string)sqlFileSpecRow[2]; | ||
347 | |||
348 | if (null != sqlFileSpecRow[3]) | ||
349 | { | ||
350 | sqlFileSpec.Size = (string)sqlFileSpecRow[3]; | ||
351 | } | ||
352 | |||
353 | if (null != sqlFileSpecRow[4]) | ||
354 | { | ||
355 | sqlFileSpec.MaxSize = (string)sqlFileSpecRow[4]; | ||
356 | } | ||
357 | |||
358 | if (null != sqlFileSpecRow[5]) | ||
359 | { | ||
360 | sqlFileSpec.GrowthSize = (string)sqlFileSpecRow[5]; | ||
361 | } | ||
362 | |||
363 | sqlDatabase.AddChild(sqlFileSpec); | ||
364 | } | ||
365 | else | ||
366 | { | ||
367 | this.Core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, sqlDatabaseTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "FileSpec_", (string)row[6], "SqlFileSpec")); | ||
368 | } | ||
369 | } | ||
370 | |||
371 | if (null != row[7]) | ||
372 | { | ||
373 | Row sqlFileSpecRow = (Row)sqlFileSpecRows[row[7]]; | ||
374 | |||
375 | if (null != sqlFileSpecRow) | ||
376 | { | ||
377 | Sql.SqlLogFileSpec sqlLogFileSpec = new Sql.SqlLogFileSpec(); | ||
378 | |||
379 | sqlLogFileSpec.Id = (string)sqlFileSpecRow[0]; | ||
380 | |||
381 | if (null != sqlFileSpecRow[1]) | ||
382 | { | ||
383 | sqlLogFileSpec.Name = (string)sqlFileSpecRow[1]; | ||
384 | } | ||
385 | |||
386 | sqlLogFileSpec.Filename = (string)sqlFileSpecRow[2]; | ||
387 | |||
388 | if (null != sqlFileSpecRow[3]) | ||
389 | { | ||
390 | sqlLogFileSpec.Size = (string)sqlFileSpecRow[3]; | ||
391 | } | ||
392 | |||
393 | if (null != sqlFileSpecRow[4]) | ||
394 | { | ||
395 | sqlLogFileSpec.MaxSize = (string)sqlFileSpecRow[4]; | ||
396 | } | ||
397 | |||
398 | if (null != sqlFileSpecRow[5]) | ||
399 | { | ||
400 | sqlLogFileSpec.GrowthSize = (string)sqlFileSpecRow[5]; | ||
401 | } | ||
402 | |||
403 | sqlDatabase.AddChild(sqlLogFileSpec); | ||
404 | } | ||
405 | else | ||
406 | { | ||
407 | this.Core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, sqlDatabaseTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "FileSpec_Log", (string)row[7], "SqlFileSpec")); | ||
408 | } | ||
409 | } | ||
410 | } | ||
411 | } | ||
412 | } | ||
413 | |||
414 | /// <summary> | ||
415 | /// Finalize the SqlScript table. | ||
416 | /// </summary> | ||
417 | /// <param name="tables">The collection of all tables.</param> | ||
418 | /// <remarks> | ||
419 | /// The SqlScript and SqlString tables contain a foreign key into the SqlDatabase | ||
420 | /// and Component tables. Depending upon the parent of the SqlDatabase | ||
421 | /// element, the SqlScript and SqlString elements are nested under either the | ||
422 | /// SqlDatabase or the Component element. | ||
423 | /// </remarks> | ||
424 | private void FinalizeSqlScriptAndSqlStringTables(TableIndexedCollection tables) | ||
425 | { | ||
426 | Table sqlDatabaseTable = tables["SqlDatabase"]; | ||
427 | Table sqlScriptTable = tables["SqlScript"]; | ||
428 | Table sqlStringTable = tables["SqlString"]; | ||
429 | |||
430 | Hashtable sqlDatabaseRows = new Hashtable(); | ||
431 | |||
432 | // index each SqlDatabase row by its primary key | ||
433 | if (null != sqlDatabaseTable) | ||
434 | { | ||
435 | foreach (Row row in sqlDatabaseTable.Rows) | ||
436 | { | ||
437 | sqlDatabaseRows.Add(row[0], row); | ||
438 | } | ||
439 | } | ||
440 | |||
441 | if (null != sqlScriptTable) | ||
442 | { | ||
443 | foreach (Row row in sqlScriptTable.Rows) | ||
444 | { | ||
445 | Sql.SqlScript sqlScript = (Sql.SqlScript)this.Core.GetIndexedElement(row); | ||
446 | |||
447 | Row sqlDatabaseRow = (Row)sqlDatabaseRows[row[1]]; | ||
448 | string databaseComponent = (string)sqlDatabaseRow[4]; | ||
449 | |||
450 | // determine if the SqlScript element should be nested under the database or another component | ||
451 | if (null != databaseComponent && databaseComponent == (string)row[2]) | ||
452 | { | ||
453 | Sql.SqlDatabase sqlDatabase = (Sql.SqlDatabase)this.Core.GetIndexedElement(sqlDatabaseRow); | ||
454 | |||
455 | sqlDatabase.AddChild(sqlScript); | ||
456 | } | ||
457 | else // nest under the component of the SqlDatabase row | ||
458 | { | ||
459 | Wix.Component component = (Wix.Component)this.Core.GetIndexedElement("Component", (string)row[2]); | ||
460 | |||
461 | // set the Database value | ||
462 | sqlScript.SqlDb = (string)row[1]; | ||
463 | |||
464 | if (null != component) | ||
465 | { | ||
466 | component.AddChild(sqlScript); | ||
467 | } | ||
468 | else | ||
469 | { | ||
470 | this.Core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, sqlScriptTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", (string)row[2], "Component")); | ||
471 | } | ||
472 | } | ||
473 | } | ||
474 | } | ||
475 | |||
476 | if (null != sqlStringTable) | ||
477 | { | ||
478 | foreach (Row row in sqlStringTable.Rows) | ||
479 | { | ||
480 | Sql.SqlString sqlString = (Sql.SqlString)this.Core.GetIndexedElement(row); | ||
481 | |||
482 | Row sqlDatabaseRow = (Row)sqlDatabaseRows[row[1]]; | ||
483 | string databaseComponent = (string)sqlDatabaseRow[4]; | ||
484 | |||
485 | // determine if the SqlScript element should be nested under the database or another component | ||
486 | if (null != databaseComponent && databaseComponent == (string)row[2]) | ||
487 | { | ||
488 | Sql.SqlDatabase sqlDatabase = (Sql.SqlDatabase)this.Core.GetIndexedElement(sqlDatabaseRow); | ||
489 | |||
490 | sqlDatabase.AddChild(sqlString); | ||
491 | } | ||
492 | else // nest under the component of the SqlDatabase row | ||
493 | { | ||
494 | Wix.Component component = (Wix.Component)this.Core.GetIndexedElement("Component", (string)row[2]); | ||
495 | |||
496 | // set the Database value | ||
497 | sqlString.SqlDb = (string)row[1]; | ||
498 | |||
499 | if (null != component) | ||
500 | { | ||
501 | component.AddChild(sqlString); | ||
502 | } | ||
503 | else | ||
504 | { | ||
505 | this.Core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, sqlStringTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", (string)row[2], "Component")); | ||
506 | } | ||
507 | } | ||
508 | } | ||
509 | } | ||
510 | } | ||
511 | } | ||
512 | } | ||