aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Data/Output.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Data/Output.cs')
-rw-r--r--src/WixToolset.Data/Output.cs394
1 files changed, 394 insertions, 0 deletions
diff --git a/src/WixToolset.Data/Output.cs b/src/WixToolset.Data/Output.cs
new file mode 100644
index 00000000..b2a21c6c
--- /dev/null
+++ b/src/WixToolset.Data/Output.cs
@@ -0,0 +1,394 @@
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.Data
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Xml;
11
12 /// <summary>
13 /// Output is generated by the linker.
14 /// </summary>
15 public sealed class Output
16 {
17 public const string XmlNamespaceUri = "http://wixtoolset.org/schemas/v4/wixout";
18 private static readonly Version CurrentVersion = new Version("4.0.0.0");
19
20 private Section entrySection;
21
22 /// <summary>
23 /// Creates a new empty output object.
24 /// </summary>
25 /// <param name="sourceLineNumbers">The source line information for the output.</param>
26 public Output(SourceLineNumber sourceLineNumbers)
27 {
28 this.Sections = new List<Section>();
29 this.SourceLineNumbers = sourceLineNumbers;
30 this.SubStorages = new List<SubStorage>();
31 this.Tables = new TableIndexedCollection();
32 }
33
34 /// <summary>
35 /// Gets the entry section for the output
36 /// </summary>
37 /// <value>Entry section for the output.</value>
38 public Section EntrySection
39 {
40 get
41 {
42 return this.entrySection;
43 }
44
45 set
46 {
47 this.entrySection = value;
48 this.Codepage = value.Codepage;
49
50 switch (this.entrySection.Type)
51 {
52 case SectionType.Bundle:
53 this.Type = OutputType.Bundle;
54 break;
55 case SectionType.Product:
56 this.Type = OutputType.Product;
57 break;
58 case SectionType.Module:
59 this.Type = OutputType.Module;
60 break;
61 case SectionType.PatchCreation:
62 this.Type = OutputType.PatchCreation;
63 break;
64 case SectionType.Patch:
65 this.Type = OutputType.Patch;
66 break;
67 default:
68 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnexpectedEntrySectionType, this.entrySection.Type));
69 }
70 }
71 }
72
73 /// <summary>
74 /// Gets the type of the output.
75 /// </summary>
76 /// <value>Type of the output.</value>
77 public OutputType Type { get; set; }
78
79 /// <summary>
80 /// Gets or sets the codepage for this output.
81 /// </summary>
82 /// <value>Codepage of the output.</value>
83 public int Codepage { get; set; }
84
85 /// <summary>
86 /// Gets the sections contained in the output.
87 /// </summary>
88 /// <value>Sections in the output.</value>
89 public ICollection<Section> Sections { get; private set; }
90
91 /// <summary>
92 /// Gets the source line information for this output.
93 /// </summary>
94 /// <value>The source line information for this output.</value>
95 public SourceLineNumber SourceLineNumbers { get; private set; }
96
97 /// <summary>
98 /// Gets the substorages in this output.
99 /// </summary>
100 /// <value>The substorages in this output.</value>
101 public ICollection<SubStorage> SubStorages { get; private set; }
102
103 /// <summary>
104 /// Gets the tables contained in this output.
105 /// </summary>
106 /// <value>Collection of tables.</value>
107 public TableIndexedCollection Tables { get; private set; }
108
109 /// <summary>
110 /// Gets the output type corresponding to a given output filename extension.
111 /// </summary>
112 /// <param name="extension">Case-insensitive output filename extension.</param>
113 /// <returns>Output type for the extension.</returns>
114 public static OutputType GetOutputType(string extension)
115 {
116 if (extension.Equals(".exe", StringComparison.OrdinalIgnoreCase))
117 {
118 return OutputType.Bundle;
119 }
120 if (extension.Equals(".msi", StringComparison.OrdinalIgnoreCase))
121 {
122 return OutputType.Product;
123 }
124 else if (extension.Equals(".msm", StringComparison.OrdinalIgnoreCase))
125 {
126 return OutputType.Module;
127 }
128 else if (extension.Equals(".msp", StringComparison.OrdinalIgnoreCase))
129 {
130 return OutputType.Patch;
131 }
132 else if (extension.Equals(".mst", StringComparison.OrdinalIgnoreCase))
133 {
134 return OutputType.Transform;
135 }
136 else if (extension.Equals(".pcp", StringComparison.OrdinalIgnoreCase))
137 {
138 return OutputType.PatchCreation;
139 }
140 else
141 {
142 return OutputType.Unknown;
143 }
144 }
145
146 /// <summary>
147 /// Gets the filename extension corresponding to a given output type.
148 /// </summary>
149 /// <param name="type">One of the WiX output types.</param>
150 /// <returns>Filename extension for the output type, for example ".msi".</returns>
151 public static string GetExtension(OutputType type)
152 {
153 switch (type)
154 {
155 case OutputType.Bundle:
156 return ".exe";
157 case OutputType.Product:
158 return ".msi";
159 case OutputType.Module:
160 return ".msm";
161 case OutputType.Patch:
162 return ".msp";
163 case OutputType.Transform:
164 return ".mst";
165 case OutputType.PatchCreation:
166 return ".pcp";
167 default:
168 return ".wix";
169 }
170 }
171
172 /// <summary>
173 /// Loads an output from a path on disk.
174 /// </summary>
175 /// <param name="path">Path to output file saved on disk.</param>
176 /// <param name="suppressVersionCheck">Suppresses wix.dll version mismatch check.</param>
177 /// <returns>Output object.</returns>
178 public static Output Load(string path, bool suppressVersionCheck)
179 {
180 using (FileStream stream = File.OpenRead(path))
181 using (FileStructure fs = FileStructure.Read(stream))
182 {
183 if (FileFormat.Wixout != fs.FileFormat)
184 {
185 throw new WixUnexpectedFileFormatException(path, FileFormat.Wixout, fs.FileFormat);
186 }
187
188 Uri uri = new Uri(Path.GetFullPath(path));
189 using (XmlReader reader = XmlReader.Create(fs.GetDataStream(), null, uri.AbsoluteUri))
190 {
191 try
192 {
193 reader.MoveToContent();
194 return Output.Read(reader, suppressVersionCheck);
195 }
196 catch (XmlException xe)
197 {
198 throw new WixCorruptFileException(path, fs.FileFormat, xe);
199 }
200 }
201 }
202 }
203
204 /// <summary>
205 /// Saves an output to a path on disk.
206 /// </summary>
207 /// <param name="path">Path to save output file to on disk.</param>
208 public void Save(string path)
209 {
210 Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
211
212 using (FileStream stream = File.Create(path))
213 using (FileStructure fs = FileStructure.Create(stream, FileFormat.Wixout, null))
214 using (XmlWriter writer = XmlWriter.Create(fs.GetDataStream()))
215 {
216 writer.WriteStartDocument();
217 this.Write(writer);
218 writer.WriteEndDocument();
219 }
220 }
221
222 /// <summary>
223 /// Processes an XmlReader and builds up the output object.
224 /// </summary>
225 /// <param name="reader">Reader to get data from.</param>
226 /// <param name="suppressVersionCheck">Suppresses wix.dll version mismatch check.</param>
227 /// <returns>The Output represented by the Xml.</returns>
228 internal static Output Read(XmlReader reader, bool suppressVersionCheck)
229 {
230 if (!reader.LocalName.Equals("wixOutput"))
231 {
232 throw new XmlException();
233 }
234
235 bool empty = reader.IsEmptyElement;
236 Output output = new Output(SourceLineNumber.CreateFromUri(reader.BaseURI));
237 SectionType sectionType = SectionType.Unknown;
238 Version version = null;
239
240 while (reader.MoveToNextAttribute())
241 {
242 switch (reader.LocalName)
243 {
244 case "codepage":
245 output.Codepage = Convert.ToInt32(reader.Value, CultureInfo.InvariantCulture.NumberFormat);
246 break;
247 case "type":
248 switch (reader.Value)
249 {
250 case "Bundle":
251 output.Type = OutputType.Bundle;
252 sectionType = SectionType.Bundle;
253 break;
254 case "Module":
255 output.Type = OutputType.Module;
256 sectionType = SectionType.Module;
257 break;
258 case "Patch":
259 output.Type = OutputType.Patch;
260 break;
261 case "PatchCreation":
262 output.Type = OutputType.PatchCreation;
263 sectionType = SectionType.PatchCreation;
264 break;
265 case "Product":
266 output.Type = OutputType.Product;
267 sectionType = SectionType.Product;
268 break;
269 case "Transform":
270 output.Type = OutputType.Transform;
271 break;
272 default:
273 throw new XmlException();
274 }
275 break;
276 case "version":
277 version = new Version(reader.Value);
278 break;
279 }
280 }
281
282 if (!suppressVersionCheck && null != version && !Output.CurrentVersion.Equals(version))
283 {
284 throw new WixException(WixDataErrors.VersionMismatch(SourceLineNumber.CreateFromUri(reader.BaseURI), "wixOutput", version.ToString(), Output.CurrentVersion.ToString()));
285 }
286
287 // create a section for all the rows to belong to
288 output.entrySection = new Section(null, sectionType, output.Codepage);
289
290 // loop through the rest of the xml building up the Output object
291 TableDefinitionCollection tableDefinitions = null;
292 List<Table> tables = new List<Table>();
293 if (!empty)
294 {
295 bool done = false;
296
297 // loop through all the fields in a row
298 while (!done && reader.Read())
299 {
300 switch (reader.NodeType)
301 {
302 case XmlNodeType.Element:
303 switch (reader.LocalName)
304 {
305 case "subStorage":
306 output.SubStorages.Add(SubStorage.Read(reader));
307 break;
308 case "table":
309 if (null == tableDefinitions)
310 {
311 throw new XmlException();
312 }
313 tables.Add(Table.Read(reader, output.entrySection, tableDefinitions));
314 break;
315 case "tableDefinitions":
316 tableDefinitions = TableDefinitionCollection.Read(reader);
317 break;
318 default:
319 throw new XmlException();
320 }
321 break;
322 case XmlNodeType.EndElement:
323 done = true;
324 break;
325 }
326 }
327
328 if (!done)
329 {
330 throw new XmlException();
331 }
332 }
333
334 output.Tables = new TableIndexedCollection(tables);
335 return output;
336 }
337
338 /// <summary>
339 /// Ensure this output contains a particular table.
340 /// </summary>
341 /// <param name="tableDefinition">Definition of the table that should exist.</param>
342 /// <param name="section">Optional section to use for the table. If one is not provided, the entry section will be used.</param>
343 /// <returns>The table in this output.</returns>
344 public Table EnsureTable(TableDefinition tableDefinition, Section section = null)
345 {
346 Table table;
347 if (!this.Tables.TryGetTable(tableDefinition.Name, out table))
348 {
349 table = new Table(section ?? this.entrySection, tableDefinition);
350 this.Tables.Add(table);
351 }
352
353 return table;
354 }
355
356 /// <summary>
357 /// Persists an output in an XML format.
358 /// </summary>
359 /// <param name="writer">XmlWriter where the Output should persist itself as XML.</param>
360 internal void Write(XmlWriter writer)
361 {
362 writer.WriteStartElement("wixOutput", XmlNamespaceUri);
363
364 writer.WriteAttributeString("type", this.Type.ToString());
365
366 if (0 != this.Codepage)
367 {
368 writer.WriteAttributeString("codepage", this.Codepage.ToString(CultureInfo.InvariantCulture));
369 }
370
371 writer.WriteAttributeString("version", Output.CurrentVersion.ToString());
372
373 // Collect all the table definitions and write them.
374 TableDefinitionCollection tableDefinitions = new TableDefinitionCollection();
375 foreach (Table table in this.Tables)
376 {
377 tableDefinitions.Add(table.Definition);
378 }
379 tableDefinitions.Write(writer);
380
381 foreach (Table table in this.Tables.OrderBy(t => t.Name))
382 {
383 table.Write(writer);
384 }
385
386 foreach (SubStorage subStorage in this.SubStorages)
387 {
388 subStorage.Write(writer);
389 }
390
391 writer.WriteEndElement();
392 }
393 }
394}