// 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.
namespace WixToolset.Data
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using SimpleJson;
///
/// Container class for an intermediate object.
///
public sealed class Intermediate
{
public const string XmlNamespaceUri = "http://wixtoolset.org/schemas/v4/wixobj";
private static readonly Version CurrentVersion = new Version("4.0.0.0");
private readonly Dictionary localizationsByCulture;
///
/// Instantiate a new Intermediate.
///
public Intermediate()
{
this.Id = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('=').Replace('+', '.').Replace('/', '_');
this.EmbedFilePaths = new List();
this.localizationsByCulture = new Dictionary(StringComparer.OrdinalIgnoreCase);
this.Sections = new List();
}
public Intermediate(string id, IEnumerable sections, IDictionary localizationsByCulture, IEnumerable embedFilePaths)
{
this.Id = id;
this.EmbedFilePaths = (embedFilePaths != null) ? new List(embedFilePaths) : new List();
this.localizationsByCulture = (localizationsByCulture != null) ? new Dictionary(localizationsByCulture, StringComparer.OrdinalIgnoreCase) : new Dictionary(StringComparer.OrdinalIgnoreCase);
this.Sections = (sections != null) ? new List(sections) : new List();
}
///
/// Get the id for the intermediate.
///
public string Id { get; }
///
/// Get the embed file paths in this intermediate.
///
public IList EmbedFilePaths { get; }
///
/// Get the localizations contained in this intermediate.
///
public IEnumerable Localizations => this.localizationsByCulture.Values;
///
/// Get the sections contained in this intermediate.
///
public IList Sections { get; }
///
/// Loads an intermediate from a path on disk.
///
/// Path to intermediate file saved on disk.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(string path, bool suppressVersionCheck = false)
{
using (var stream = File.OpenRead(path))
{
var uri = new Uri(Path.GetFullPath(path));
var creator = new SimpleTupleDefinitionCreator();
return Intermediate.LoadIntermediate(stream, uri, creator, suppressVersionCheck);
}
}
///
/// Loads an intermediate from a stream.
///
/// Assembly with intermediate embedded in resource stream.
/// Name of resource stream.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(Assembly assembly, string resourceName, bool suppressVersionCheck = false)
{
var creator = new SimpleTupleDefinitionCreator();
return Intermediate.Load(assembly, resourceName, creator, suppressVersionCheck);
}
///
/// Loads an intermediate from a stream.
///
/// Assembly with intermediate embedded in resource stream.
/// Name of resource stream.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(Assembly assembly, string resourceName, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
{
using (var resourceStream = assembly.GetManifestResourceStream(resourceName))
{
var uriBuilder = new UriBuilder(assembly.CodeBase);
uriBuilder.Scheme = "embeddedresource";
uriBuilder.Fragment = resourceName;
return Intermediate.LoadIntermediate(resourceStream, uriBuilder.Uri, creator, suppressVersionCheck);
}
}
///
/// Loads an intermediate from a path on disk.
///
/// Path to intermediate file saved on disk.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(string path, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
{
using (var stream = File.OpenRead(path))
{
var uri = new Uri(Path.GetFullPath(path));
return Intermediate.LoadIntermediate(stream, uri, creator, suppressVersionCheck);
}
}
///
/// Loads several intermediates from paths on disk using the same definitions.
///
/// Paths to intermediate files saved on disk.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediates
public static IEnumerable Load(IEnumerable intermediateFiles, bool suppressVersionCheck = false)
{
var creator = new SimpleTupleDefinitionCreator();
return Intermediate.Load(intermediateFiles, creator);
}
///
/// Loads several intermediates from paths on disk using the same definitions.
///
/// Paths to intermediate files saved on disk.
/// ITupleDefinitionCreator to use when reconstituting the intermediates.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediates
public static IEnumerable Load(IEnumerable intermediateFiles, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
{
var jsons = new Queue();
var intermediates = new List();
foreach (var path in intermediateFiles)
{
using (var stream = File.OpenRead(path))
{
var uri = new Uri(Path.GetFullPath(path));
var json = Intermediate.LoadJson(stream, uri, suppressVersionCheck);
Intermediate.LoadDefinitions(json, creator);
jsons.Enqueue(new JsonWithPath { Json = json, Path = uri });
}
}
while (jsons.Count > 0)
{
var jsonWithPath = jsons.Dequeue();
var intermediate = Intermediate.FinalizeLoad(jsonWithPath.Json, jsonWithPath.Path, creator);
intermediates.Add(intermediate);
}
return intermediates;
}
///
/// Saves an intermediate to a path on disk.
///
/// Path to save intermediate file to disk.
public void Save(string path)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
using (var stream = File.Create(path))
using (var fs = FileStructure.Create(stream, FileFormat.WixIR, this.EmbedFilePaths))
using (var writer = new StreamWriter(fs.GetDataStream()))
{
var jsonObject = new JsonObject
{
{ "id", this.Id },
{ "version", Intermediate.CurrentVersion.ToString() }
};
var sectionsJson = new JsonArray(this.Sections.Count);
foreach (var section in this.Sections)
{
var sectionJson = section.Serialize();
sectionsJson.Add(sectionJson);
}
jsonObject.Add("sections", sectionsJson);
var customDefinitions = this.GetCustomDefinitionsInSections();
if (customDefinitions.Count > 0)
{
var customDefinitionsJson = new JsonArray(customDefinitions.Count);
foreach (var kvp in customDefinitions.OrderBy(d => d.Key))
{
var customDefinitionJson = kvp.Value.Serialize();
customDefinitionsJson.Add(customDefinitionJson);
}
jsonObject.Add("definitions", customDefinitionsJson);
}
if (this.Localizations.Any())
{
var localizationsJson = new JsonArray();
foreach (var localization in this.Localizations)
{
var localizationJson = localization.Serialize();
localizationsJson.Add(localizationJson);
}
jsonObject.Add("localizations", localizationsJson);
}
var json = SimpleJson.SerializeObject(jsonObject);
writer.Write(json);
}
}
///
/// Loads an intermediate from a path on disk.
///
/// Stream to intermediate file.
/// Path name of intermediate file.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
private static Intermediate LoadIntermediate(Stream stream, Uri baseUri, ITupleDefinitionCreator creator, bool suppressVersionCheck = false)
{
var json = Intermediate.LoadJson(stream, baseUri, suppressVersionCheck);
Intermediate.LoadDefinitions(json, creator);
return Intermediate.FinalizeLoad(json, baseUri, creator);
}
///
/// Loads json form of intermedaite from stream.
///
/// Stream to intermediate file.
/// Path name of intermediate file.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded json.
private static JsonObject LoadJson(Stream stream, Uri baseUri, bool suppressVersionCheck)
{
JsonObject jsonObject;
using (var fs = FileStructure.Read(stream))
{
if (FileFormat.WixIR != fs.FileFormat)
{
throw new WixUnexpectedFileFormatException(baseUri.LocalPath, FileFormat.WixIR, fs.FileFormat);
}
var json = fs.GetData();
jsonObject = SimpleJson.DeserializeObject(json) as JsonObject;
}
if (!suppressVersionCheck)
{
var versionJson = jsonObject.GetValueOrDefault("version");
if (!Version.TryParse(versionJson, out var version) || !Intermediate.CurrentVersion.Equals(version))
{
throw new WixException(ErrorMessages.VersionMismatch(SourceLineNumber.CreateFromUri(baseUri.AbsoluteUri), "intermediate", versionJson, Intermediate.CurrentVersion.ToString()));
}
}
return jsonObject;
}
///
/// Loads custom definitions in intermediate json into the creator.
///
/// Json version of intermediate.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
private static void LoadDefinitions(JsonObject json, ITupleDefinitionCreator creator)
{
var definitionsJson = json.GetValueOrDefault("definitions");
if (definitionsJson != null)
{
foreach (JsonObject definitionJson in definitionsJson)
{
var definition = IntermediateTupleDefinition.Deserialize(definitionJson);
creator.AddCustomTupleDefinition(definition);
}
}
}
///
/// Loads the sections and localization for the intermediate.
///
/// Json version of intermediate.
/// Path to the intermediate.
/// ITupleDefinitionCreator to use when reconstituting the intermediate.
/// The finalized intermediate.
private static Intermediate FinalizeLoad(JsonObject json, Uri baseUri, ITupleDefinitionCreator creator)
{
var id = json.GetValueOrDefault("id");
var sections = new List();
var sectionsJson = json.GetValueOrDefault("sections");
foreach (JsonObject sectionJson in sectionsJson)
{
var section = IntermediateSection.Deserialize(creator, baseUri, sectionJson);
sections.Add(section);
}
var localizations = new Dictionary(StringComparer.OrdinalIgnoreCase);
var localizationsJson = json.GetValueOrDefault("localizations") ?? new JsonArray();
foreach (JsonObject localizationJson in localizationsJson)
{
var localization = Localization.Deserialize(localizationJson);
localizations.Add(localization.Culture, localization);
}
return new Intermediate(id, sections, localizations, null);
}
#if false
///
/// Loads an intermediate from a path on disk.
///
/// Path to intermediate file saved on disk.
/// Collection containing TableDefinitions to use when reconstituting the intermediate.
/// Suppress checking for wix.dll version mismatches.
/// Returns the loaded intermediate.
public static Intermediate Load(string path, TableDefinitionCollection tableDefinitions, bool suppressVersionCheck)
{
using (FileStream stream = File.OpenRead(path))
using (FileStructure fs = FileStructure.Read(stream))
{
if (FileFormat.Wixobj != fs.FileFormat)
{
throw new WixUnexpectedFileFormatException(path, FileFormat.Wixobj, fs.FileFormat);
}
Uri uri = new Uri(Path.GetFullPath(path));
using (XmlReader reader = XmlReader.Create(fs.GetDataStream(), null, uri.AbsoluteUri))
{
try
{
reader.MoveToContent();
return Intermediate.Read(reader, tableDefinitions, suppressVersionCheck);
}
catch (XmlException xe)
{
throw new WixCorruptFileException(path, fs.FileFormat, xe);
}
}
}
}
///
/// Saves an intermediate to a path on disk.
///
/// Path to save intermediate file to disk.
public void Save(string path)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
using (var stream = File.Create(path))
using (var fs = FileStructure.Create(stream, FileFormat.Wixobj, null))
using (var writer = XmlWriter.Create(fs.GetDataStream()))
{
writer.WriteStartDocument();
this.Write(writer);
writer.WriteEndDocument();
}
}
///
/// Parse an intermediate from an XML format.
///
/// XmlReader where the intermediate is persisted.
/// TableDefinitions to use in the intermediate.
/// Suppress checking for wix.dll version mismatch.
/// The parsed Intermediate.
private static Intermediate Read(XmlReader reader, TableDefinitionCollection tableDefinitions, bool suppressVersionCheck)
{
if ("wixObject" != reader.LocalName)
{
throw new XmlException();
}
bool empty = reader.IsEmptyElement;
Version objVersion = null;
string id = null;
while (reader.MoveToNextAttribute())
{
switch (reader.LocalName)
{
case "version":
objVersion = new Version(reader.Value);
break;
case "id":
id = reader.Value;
break;
}
}
if (!suppressVersionCheck && null != objVersion && !Intermediate.CurrentVersion.Equals(objVersion))
{
throw new WixException(WixDataErrors.VersionMismatch(SourceLineNumber.CreateFromUri(reader.BaseURI), "object", objVersion.ToString(), Intermediate.CurrentVersion.ToString()));
}
Intermediate intermediate = new Intermediate();
intermediate.id = id;
if (!empty)
{
bool done = false;
while (!done && reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
switch (reader.LocalName)
{
case "section":
intermediate.AddSection(Section.Read(reader, tableDefinitions));
break;
default:
throw new XmlException();
}
break;
case XmlNodeType.EndElement:
done = true;
break;
}
}
if (!done)
{
throw new XmlException();
}
}
return intermediate;
}
///
/// Persists an intermediate in an XML format.
///
/// XmlWriter where the Intermediate should persist itself as XML.
private void Write(XmlWriter writer)
{
writer.WriteStartElement("wixObject", XmlNamespaceUri);
writer.WriteAttributeString("version", Intermediate.CurrentVersion.ToString());
writer.WriteAttributeString("id", this.id);
foreach (Section section in this.Sections)
{
section.Write(writer);
}
writer.WriteEndElement();
}
#endif
private Dictionary GetCustomDefinitionsInSections()
{
var customDefinitions = new Dictionary();
foreach (var tuple in this.Sections.SelectMany(s => s.Tuples).Where(t => t.Definition.Type == TupleDefinitionType.MustBeFromAnExtension))
{
if (!customDefinitions.ContainsKey(tuple.Definition.Name))
{
customDefinitions.Add(tuple.Definition.Name, tuple.Definition);
}
}
return customDefinitions;
}
private struct JsonWithPath
{
public JsonObject Json { get; set; }
public Uri Path { get; set; }
}
}
}