Skip to content

Instantly share code, notes, and snippets.

@kamranayub
Created February 26, 2012 18:02
Show Gist options
  • Save kamranayub/1918022 to your computer and use it in GitHub Desktop.
Save kamranayub/1918022 to your computer and use it in GitHub Desktop.
MSBuild Inline Task to Transform a Hierarchy of XML Files using XDT Transforms
<?xml version="1.0"?>
<!-- Base XML file; shared stuff -->
<foo xmlns="somens">
<baz />
</foo>
<?xml version="1.0"?>
<!-- inherits is relative to the XML file's directory (e.g. ../base.xml would look one directory up) -->
<foo inherits="base.xml" xmlns="somens" xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<bar xdt:Transform="Insert" />
</foo>
<!-- This task takes in a XDT transform file and transforms it, following any inheritance chain.
There should be at least one base transform for this to work; otherwise just use Microsoft's
regular TransformXml task. -->
<!-- EXAMPLE USAGE:
<TransformXmlHierarchy
Source="source.xml"
Destination="transformed.xml"
TaskDirectory="path/to/directory/of/Microsoft.Web.Publishing.Tasks" />
-->
<UsingTask
TaskName="TransformXmlHierarchy"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<Source Required="true" />
<Destination Required="true" />
<TaskDirectory Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Using Namespace="System"/>
<Using Namespace="System.Linq"/>
<Using Namespace="System.IO" />
<Using Namespace="System.Xml"/>
<Using Namespace="System.Reflection" />
<Code Type="Fragment" Language="cs">
<![CDATA[
// TODO: Figure out a way to make the inline task reference this. For the life of me,
// it wasn't working, so had to load via Reflection.
var taskPath = Path.Combine(TaskDirectory, "Microsoft.Web.Publishing.Tasks.dll");
if (!File.Exists(taskPath))
throw new Exception("Could not load publishing tasks assembly");
Assembly taskAssembly = Assembly.UnsafeLoadFrom(taskPath);
Func<XmlDocument, string, XmlDocument> transformer = (source, transform) =>
{
dynamic transformation = taskAssembly.CreateInstance(
"Microsoft.Web.Publishing.Tasks.XmlTransformation", true, BindingFlags.CreateInstance,
null, new object[] { transform }, null, null);
if (transformation == null)
throw new Exception("Could not create instance of XmlTransformation");
transformation.Apply(source);
return source;
};
Func<XmlDocument, string> getParent = (source) =>
{
if (source == null) return null;
// Use default namespace of document
var nsmgr = new XmlNamespaceManager(source.NameTable);
nsmgr.AddNamespace("x", source.DocumentElement.NamespaceURI);
// TODO: Probably can safely select first node, to support any kind of XML document
var attr = source.SelectSingleNode("x:package", nsmgr).Attributes["inherits"];
return attr == null ? null : attr.Value;
};
var rootDoc = new XmlDocument();
var sources = new List<string>();
var basePath = Path.GetDirectoryName(Source);
var parent = Path.GetFileName(Source);
if (basePath == null) {
throw new Exception("Could not find base directory of path " + Source);
}
do {
sources.Add(parent);
rootDoc.Load(Path.Combine(basePath, parent));
parent = getParent(rootDoc);
// TODO: Need to rebase basePath here?
if (parent != null) {
rootDoc.Load(Path.Combine(basePath, parent));
}
} while (parent != null);
// Reverse chain
sources.Reverse();
var transformedDoc = sources.Skip(1).Aggregate(rootDoc,
(document, transform) => String.IsNullOrEmpty(transform)
? document
: transformer(document, Path.Combine(basePath, transform)),
(document) => document);
Log.LogMessage(MessageImportance.Normal, "Transformed " + Destination);
transformedDoc.Save(Destination);
]]>
</Code>
</Task>
</UsingTask>
<Import Project="Transforms.xml" />
<Target name="Foo">
<TransformXmlHierarchy
Source="source.xml"
Destination="transformed\transformed.xml"
TaskDirectory="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\" />
</Target>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment