Calendar

<<  May 2017  >>
MonTueWedThuFriSatSun
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar

RecentComments

None

 
 
     
 
A different way to display the output from Get-DirAsXml is with Xaml. I will probably occasionally refer back to the previous posts on how to do it using Windows.Forms. So here is step one, displaying the output as a simple TreeView function Xaml-DisplayDir{    param([xml]$xml)    $xaml = @" <Window     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     Title="Xaml-DisplayDir"     Height="300" Width="600"     ResizeMode="CanResizeWithGrip"     WindowStyle="SingleBorderWindow">    <Window.Resources>        <XmlDataProvider x:Key="DirectoryData" XPath="/DAXContainer/*">            <x:XData>                <DAXContainer xmlns="">                    $($xml.SelectSingleNode("/*").OuterXml)                </DAXContainer>            </x:XData>        </XmlDataProvider>        <HierarchicalDataTemplate x:Key="FolderTemplate" ItemsSource="{Binding XPath=folder}">            <StackPanel Orientation="Horizontal">                <TextBlock Text="{Binding XPath=@Name}" Margin="2,0,0,0" />            </StackPanel>        </HierarchicalDataTemplate>        <DataTemplate x:Key="FileTemplate">            <StackPanel Orientation="Horizontal">                <TextBlock Text="{Binding XPath=@Name}" Margin="0,0,0,0" />            </StackPanel>        </DataTemplate>    </Window.Resources>    <Grid>        <TreeView Margin="0, 0, 0, 0" Name="TREEVIEW" HorizontalAlignment="Stretch" DataContext="{Binding Source={StaticResource DirectoryData}, XPath=/DAXContainer/*}" ItemTemplate="{StaticResource FolderTemplate}" ItemsSource="{Binding}" />    </Grid></Window>"@    $RESULT = ""    $WINDOW = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader([xml]$xaml)))    $TREEVIEW = $WINDOW.FindName("TREEVIEW")        $TREEVIEW.add_SelectedItemChanged({            $RESULT = $this.Selecteditem.Name            write-host "SelectedItemChanged"    })    [void]$WINDOW.ShowDialog()    $WINDOW.Close()    $WINDOW.Dispose()    $RESULT} There are a few things going on here. The whole thing is a Powershell Mashup script. It declares a here-string that contains the Xaml markup. It inserts the Xml into the middle of the Xaml markup. (highlighted) It creates a Window object from the resulting markup. It adds a simple event handler to the TREEVIEW object. Displays the result. I like to think of it like this. Get-DirAsXml is used to get some Xml, the Xml is docorated or transformed (step 3 above. The Xml is wrapped in Xaml), the resulting Xml is displayed in a window. It can be used like this PS> . .\Xaml-DisplayDir.ps1 # dot source the scriptPS> Xaml-DisplayDir (Get-DirAsXml test) It might be displayed like this Here is the code Xaml-DisplayDir.zip (1.09 kb) So onto the next step. Add a ListView to display the files. This code will do it <ListView Name="LISTVIEW" Grid.Column="2" HorizontalAlignment="Stretch" ItemsSource="{Binding Path=SelectedItem, ElementName=TREEVIEW, Mode=OneWay}">    <ListView.View>        <GridView AllowsColumnReorder="true" ColumnHeaderContainerStyle="{DynamicResource CustomHeaderStyle}">            <GridViewColumn Header="Name" CellTemplate="{StaticResource ItemTemplate}" />            <GridViewColumn DisplayMemberBinding="{Binding XPath=@Length}" Header="Length" />            <GridViewColumn DisplayMemberBinding="{Binding XPath=@LastWriteTime}" Header="Date" />        </GridView>    </ListView.View></ListView> This adds a ListView with 3 columns Name, Length and Date. The columns will display the data specified in the XPath binding i.e. @Name, @Length, @LastWriteTime. We don't need to do anything like add an event handler to populate and display the items in the list because the ListView.ItemSource attribute is 'bound' to the TREEVIEW.SelectedItem. We also want to add icons for the folders and files. Rather than using an ImageList and indexing into it like Forms, Xaml is a bit more like html and uses markup to display an image and the text. Like this StackPanel <DataTemplate xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Key="ItemTemplate">    <StackPanel Orientation="Horizontal">        <Image Width="16" Height="16" Stretch="Fill" Source="ico2.png" />        <TextBlock Text="{Binding XPath=@Name}" Margin="0,0,0,0" />    </StackPanel></DataTemplate> It might be displayed like this You will notice that the folder test2 in the listview has the wrong icon. This can be fixed with a Trigger. <Style TargetType="Image">    <Style.Triggers>        <DataTrigger Binding="{Binding Path=Name}" Value="folder">            <Setter Property="Source" Value="{StaticResource FolderImage}" />        </DataTrigger>        <DataTrigger Binding="{Binding Path=Name}" Value="file">            <Setter Property="Source" Value="{StaticResource FileImage}" />        </DataTrigger>    </Style.Triggers></Style> I would like to bind the DataTrigger to XPath=local-name() but XPath functions are not supported DOH! To keep things self contained I have set the Image.Source property to rather badly drawn Xaml icons. It can be used like this PS> . .\Xaml-DisplayDirExp.ps1 # dot source the scriptPS> Xaml-DisplayDirExp (Get-DirAsXml test -props @{Length="";LastWriteTime=""} ) Here is the code Xaml-DisplayDirExp.zip (2.22 kb) Because Xaml is Xml we can process it with Xslt. We can add a little identity transform to our script that has an aditional template to hande the DAXContainer [xml]$xslt = @' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">    <xsl:param name="DAX" />    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="DAXContainer">        <DAXContainer>            <xsl:copy-of select="$DAX" />        </DAXContainer>    </xsl:template></xsl:stylesheet>'@[xml]$resultxaml = (Invoke-Transform -inxml $xaml -inxsl $xslt -arguments @{'DAX'=$xml}) This is a simple T-DaxDecorate Translet. (Notice that the single quote Here-String from Powershell is used for $xslt. This is to avoid Powershell's symbol substitution that would have translated  <xsl:copy-of select="$DAX" /> into invalid Xslt.) The Invoke-Transform is taking the Xaml as the input Xml and the motherload xml is passed as a parameter and copied directly to the output. Something like this It can be used like this PS> . .\Xaml-DisplayDirExpXslt.ps1 # dot source the scriptPS> Xaml-DisplayDirExp (Get-DirAsXml test -props @{Length="";LastWriteTime=""} ) Here is the code  Xaml-DisplayDirExpXslt.zip (1.99 kb) As in the Forms version we will want to specify the columns that are displayed. So far the Name, Length and LastWriteTime are hard coded in the Xaml Form but we can pass another Xml into the mix that specifies the columns we want. Something like this PS> Xaml-DisplayDirExp (Get-DirAsXml test -props @{Length=""; LastWriteTime=""} -ExtendedProps @{Title=""; Subject=""; Author=""; Category=""; Keywords=""; Comments=""}) -columns ([xml]"<columns><c n='Length' b='@Length' /><c n='Date' b='@LastWriteTime' /></columns>") The nice thing about this is that the column name and the columns binding path can be specified. [xml]$xslt = @' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xaml="http://schemas.microsoft.com/winfx/2006/xaml/presentation" version="1.0">    <xsl:param name="COLUMNS" />    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="xaml:ListView[@Name='LISTVIEW']/xaml:ListView.View/xaml:GridView">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />            <xsl:apply-templates select="$COLUMNS/columns/c" />        </xsl:copy>    </xsl:template>    <xsl:template match="columns/c">        <xaml:GridViewColumn DisplayMemberBinding="{{Binding XPath={@b}}}" Header="{@n}" />    </xsl:template></xsl:stylesheet>'@[xml]$resultxaml = (Invoke-Transform -inxml $xaml -inxsl $xslt -arguments @{COLUMNS=$columns}) This is a simple T-DaxExplorerColumn Translet (Notice that the curly braces in the DisplayMemberBinding are escaped to avoid Xslt AVT substitution.) We can expand it to specify that we want column justification PS> Xaml-DisplayDirExp (Get-DirAsXml test -props @{Length=""; LastWriteTime=""} -ExtendedProps @{Title=""; Subject=""; Author=""; Category=""; Keywords=""; Comments=""}) -columns ([xml]"<columns><c n='Length' b='@Length' j='Right' /><c n='Date' b='@LastWriteTime' j='Right' /></columns>") [xml]$xslt = @' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xaml="http://schemas.microsoft.com/winfx/2006/xaml/presentation" version="1.0">    <xsl:param name="COLUMNS" />    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="xaml:ListView[@Name='LISTVIEW']/xaml:ListView.View/xaml:GridView">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />            <xsl:apply-templates select="$COLUMNS/columns/c" />        </xsl:copy>    </xsl:template>    <xsl:template match="columns/c">        <xaml:GridViewColumn Header="{@n}">            <xaml:GridViewColumn.CellTemplate>                <xaml:DataTemplate>                    <xaml:TextBlock Text="{{Binding XPath={@b}}}"                             TextAlignment="{@j}" />                </xaml:DataTemplate>            </xaml:GridViewColumn.CellTemplate>        </xaml:GridViewColumn>    </xsl:template></xsl:stylesheet>'@[xml]$resultxaml = (Invoke-Transform -inxml $xaml -inxsl $xslt -arguments @{COLUMNS=$columns}) This is a simple T-DaxExplorerColumnWithJustify Translet For this to work properly we need to add a Style to the ListViewItem that will contain the TextBlock. It needs to stretch to fill the grid cell. This style can go into a Resources section that is in scope. I put it into the Window.Resources section. <Style TargetType="{x:Type ListViewItem}">    <Setter Property="HorizontalContentAlignment" Value="Stretch" /></Style> It might be displayed like this. Here is the code  Xaml-DisplayDirExpXsltColumns.zip (2.21 kb) Adding a ToolTip to items in the ListView is easy. We just have to add a ToolTip property to the ItemTemplate StackPanel and bind it to the InfoTip attribute. <StackPanel.ToolTip>    <TextBlock Text="{Binding XPath=@InfoTip}" /></StackPanel.ToolTip> Adding XPath searching seems simple at first. Add a Button and a TextBox to the last Row of the Grid <Grid Grid.Row="2" Grid.ColumnSpan="3" Background="Aquamarine">    <Grid.ColumnDefinitions>        <ColumnDefinition Width="100" />        <ColumnDefinition Width="*" />    </Grid.ColumnDefinitions>    <Button Name="SEARCHBUTTON" Content="Search" Width="100" />    <TextBox Name="SEARCHTEXT" Grid.Column="2" HorizontalAlignment="Stretch" Width="Auto" /></Grid> Then add a Click event to the Button and set the result of the XPath expression to the ItemsSource propert of the ListView. This works but it wrecks the binding of the ListView and further clicking on the TreeView doesn't do anything obviously. I tried a few things but in the end I decided on a 2 ListView approach. One is visible when the search button is clicked and the other when the treeview is clicked. It isn't perfect but it works until I can find a way of recreating the binding. It might display like this Here is the code  Xaml-DisplayDirExpXsltSearch.zip (2.75 kb) Now that we have another control on the form it is worth applying some styling. We can add say a gradient to the button. <Style TargetType="{x:Type Button}">    <Setter Property="Background">        <Setter.Value>            <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">                <GradientStop Color="#FF0000FF" Offset="0" />                <GradientStop Color="#FFFFFFFF" Offset="0.338" />                <GradientStop Color="#FFFFFFFF" Offset="0.716" />                <GradientStop Color="#FF0000FF" Offset="1" />            </LinearGradientBrush>        </Setter.Value>    </Setter></Style> Styles can get very big very quickly and a way of keeping them seperate is to include them like the columns and DAXml by doing a merge in Xslt   [xml]$xslt = @' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xaml="http://schemas.microsoft.com/winfx/2006/xaml/presentation" version="1.0">    <xsl:param name="STYLE" />    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="xaml:Window.Resources">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />            <xsl:apply-templates select="$STYLE/*/*" />        </xsl:copy>    </xsl:template></xsl:stylesheet>'@[xml]$resultxaml = (Invoke-Transform -inxml $xaml -inxsl $xslt -arguments @{STYLE=$style}) This is a simple T-DaxExplorerStyler Translet It might be displayed like this or    Here is the code Xaml-DisplayDirExpXsltStyle.zip (2.81 kb)  Adding Drag'n'Drop is very similar to the Forms version except there is no DragStart event and the object model is slightly different but the code is very similar. I added a third pane like before that contains a TreeView. The drag rule is simple, items can be dragged from the ListView to the right-hand TreeView. The Template for the TreeView is modified to display folders and files. The resulting Xml is put into the pipeline when the form closes. It might be displayed like this (without cursor) Here is the code Xaml-DisplayDirExpDragDrop.zip (3.44 kb)
Here is a  translet that will transform Timed Text (TT) Authoring Format 1.0  to Microsoft Synchronized Accessible Media Interchange (SAMI). This one allows you to specify a TimeShift value in milliseconds to the transform. It also does better subtitle colouring. function T-TtafToSmi{    param (  [xml]$inxml            ,[int]$TimeShift = 0                )    BEGIN {        . pslib:\xml\invoke-transform.ps1        [xml]$xslt = @' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:t="http://www.w3.org/2006/10/ttaf1"xmlns="http://www.w3.org/2006/10/ttaf1"xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"xmlns:msxsl="urn:schemas-microsoft-com:xslt"xmlns:cjb="http://www.bayes.co.uk/script" version="1.0" exclude-result-prefixes="#default t ttp tts ttm msxsl cjb">    <xsl:param name="TimeShift" select="0" />    <xsl:output encoding="utf-8" indent="yes" method="xml" omit-xml-declaration="yes" />    <msxsl:script language="C#" implements-prefix="cjb">        public double getMilliseconds(string time)        {        return (string.IsNullOrEmpty(time) ? 0 : TimeSpan.Parse(time).TotalMilliseconds);        }        public string hyphenate(string str){        Text.StringBuilder sb = new Text.StringBuilder("", 100);        foreach (char c in str){        if (char.IsUpper(c)){sb.Append("-");sb.Append(char.ToLower(c));}        else{sb.Append(c);}}        return sb.ToString();        }    </msxsl:script>    <xsl:variable name="default-style">        <xsl:for-each select="/t:tt/t:head/t:styling/t:style[last()]">            <xsl:sort order="ascending" select="count(@tts:*)" />            <xsl:value-of select="@id" />        </xsl:for-each>    </xsl:variable>    <xsl:variable name="default-class">        <xsl:value-of select="/t:tt/@xml:lang" />cc    </xsl:variable>    <xsl:template match="t:tt">        <SAMI>            <HEAD>                <TITLE>                    <xsl:value-of select="/t:tt/t:head/t:metadata/ttm:title" />                </TITLE>                <xsl:comment>                    Copyright <xsl:value-of select="/t:tt/t:head/t:metadata/ttm:copyright" />                </xsl:comment>                <xsl:copy-of select="/t:tt/t:head/comment()" />                <STYLE>                    <xsl:comment>                        p {                        text-align: center;                        font-family: arial, sans-serif;                        font-weight: normal;                        color: white;                        font-size: 16pt;                        <xsl:apply-templates select="/t:tt/t:head/t:styling/t:style[@id=$default-style]/@tts:*" />                        }                        .<xsl:value-of select="$default-class" /> {                        text-align: center;                        font-family: arial, sans-serif;                        font-weight: normal;                        color: white;                        font-size: 16pt;                        <xsl:apply-templates select="/t:tt/t:head/t:styling/t:style[@id=$default-style]/@tts:*" />                        Name: <xsl:value-of select="/t:tt/@xml:lang" /> Subtitles;                        lang: <xsl:value-of select="/t:tt/@xml:lang" />;                        SAMI_Type: CC;                        }                    </xsl:comment>                </STYLE>            </HEAD>            <xsl:apply-templates select="/t:tt/t:body" />        </SAMI>    </xsl:template>    <xsl:template match="t:body">        <BODY>            <xsl:apply-templates />        </BODY>    </xsl:template>    <xsl:template match="t:body/t:div">        <xsl:apply-templates />    </xsl:template>    <xsl:template match="t:p">        <xsl:variable name="start" select="cjb:getMilliseconds(string(@begin)) + $TimeShift" />        <xsl:variable name="end" select="cjb:getMilliseconds(string(@end)) + $TimeShift" />        <xsl:variable name="following-start" select="cjb:getMilliseconds(string(following-sibling::t:p/@begin)) + $TimeShift" />        <Sync START="{$start}">            <p>                <xsl:if test="@style">                    <xsl:attribute name="class">                        <xsl:value-of select="$default-class" />                    </xsl:attribute>                </xsl:if>                <span>                    <xsl:if test="@style=$default-style and @tts:*">                        <span>                            <xsl:attribute name="style">                                <xsl:apply-templates select="@tts:*" />                            </xsl:attribute>                        </span>                    </xsl:if>                    <xsl:if test="@style!=$default-style">                        <xsl:attribute name="style">                            <xsl:apply-templates select="/t:tt/t:head/t:styling/t:style[@id=current()/@style]/@tts:*|@tts:*" />                        </xsl:attribute>                    </xsl:if>                    <xsl:apply-templates />                </span>            </p>        </Sync>        <xsl:if test="$end &lt; ($following-start - 10)">            <xsl:text>      </xsl:text>            <Sync START="{$end}">                <p>                    <nonbreakingspace />                </p>            </Sync>        </xsl:if>    </xsl:template>    <xsl:template match="@tts:*">        <xsl:value-of select="cjb:hyphenate(local-name())" />:<xsl:value-of select="." />;    </xsl:template>    <xsl:template match="@tts:fontFamily">        <xsl:value-of select="cjb:hyphenate(local-name())" />:<xsl:value-of select="cjb:hyphenate(.)" />;    </xsl:template>    <xsl:template match="@tts:fontSize">        <xsl:value-of select="cjb:hyphenate(local-name())" />:<xsl:value-of select="." /> pt;    </xsl:template>    <xsl:template match="*">        <xsl:element name="{local-name(.)}">            <xsl:if test="@tts:*">                <xsl:attribute name="style">                    <xsl:apply-templates select="@tts:*" />                </xsl:attribute>            </xsl:if>            <xsl:apply-templates select="@*[namespace-uri()!='http://www.w3.org/2006/10/ttaf1#style']" />            <xsl:apply-templates />        </xsl:element>    </xsl:template>    <xsl:template match="text()">        <xsl:copy />    </xsl:template>    <xsl:template match="@*">        <xsl:attribute name="{local-name()}">            <xsl:value-of select="." />        </xsl:attribute>    </xsl:template></xsl:stylesheet>'@        }    PROCESS{        if ($_ -is [xml]){            $result = (invoke-transform -inxml $_ -inxsl $xslt -arguments @{TimeShift=$TimeShift})            $result = $result.replace(" xmlns=`"http://www.w3.org/2006/10/ttaf1`"", "")            $result = $result.replace("<nonbreakingspace />", "&nbsp;")            $result        }    END{        if ($inxml -is [xml]){            $result = (invoke-transform -inxml $inxml -inxsl $xslt -arguments @{TimeShift=$TimeShift})            $result = $result.replace(" xmlns=`"http://www.w3.org/2006/10/ttaf1`"", "")            $result = $result.replace("<nonbreakingspace />", "&nbsp;")            $result        }    }} The replaces on the $result string look like a bit of a hack but they do need to be there for reasons that are very long to explain. It can be used like this PS> . .\T-TtafToSmiM3.ps1 # dot source this filePS> T-TtafToSmi [xml](gc ttaf.xml) -T 1000| sc -encoding ascii or PS> . .\T-TtafToSmiM3.ps1 # dot source this filePS> [xml](gc .\ttaf.xml) | T-TtafToSmi -T 1000 | sc -encoding ascii The -T 1000 will shift the subtitle forward by 1 second. A value of -T -1000 will shift the subtitle backward by 1 second. Here is the code  T-TtafToSmiM3.zip (2.50 kb)
Here is a  translet that will transform Timed Text (TT) Authoring Format 1.0  to Microsoft Synchronized Accessible Media Interchange (SAMI). This one is quite simple. All text is the same colour. Not much different from SRT format really. function T-TtafToSmi{    param ($inxml)    BEGIN {        . pslib:\xml\invoke-transform.ps1       [xml]$xslt = @' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:t="http://www.w3.org/2006/10/ttaf1"xmlns="http://www.w3.org/2006/10/ttaf1"xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"xmlns:msxsl="urn:schemas-microsoft-com:xslt"xmlns:cjb="http://www.bayes.co.uk/script"version="1.0"exclude-result-prefixes="#default t ttp tts ttm msxsl cjb">    <xsl:output encoding="utf-8" indent="yes" method="xml" omit-xml-declaration="yes" />    <msxsl:script language="C#" implements-prefix="cjb">        public string getMilliseconds(string time)        {        return (string.IsNullOrEmpty(time) ? "" : TimeSpan.Parse(time).TotalMilliseconds.ToString());        }        public string hyphenate(string str){        Text.StringBuilder sb = new Text.StringBuilder("", 100);        foreach (char c in str){        if (char.IsUpper(c)){sb.Append("-");sb.Append(char.ToLower(c));}        else{sb.Append(c);}}        return sb.ToString();        }    </msxsl:script>    <xsl:variable name="default-style">        <xsl:for-each select="/t:tt/t:head/t:styling/t:style[last()]">            <xsl:sort order="ascending" select="count(@tts:*)" />            <xsl:value-of select="@id" />        </xsl:for-each>    </xsl:variable>    <xsl:variable name="default-class">        <xsl:value-of select="/t:tt/@xml:lang" />cc    </xsl:variable>    <xsl:template match="t:tt">        <SAMI>            <HEAD>                <TITLE>                    <xsl:value-of select="/t:tt/t:head/t:metadata/ttm:title" />                </TITLE>                <xsl:comment>                    Copyright <xsl:value-of select="/t:tt/t:head/t:metadata/ttm:copyright" />                </xsl:comment>                <xsl:copy-of select="/t:tt/t:head/comment()" />                <STYLE>                    <xsl:comment>                        .<xsl:value-of select="$default-class" /> {                        text-align: center;                        font-family: arial, sans-serif;                        font-weight: normal;                        color: white;                        font-size: 16pt;                        Name: <xsl:value-of select="/t:tt/@xml:lang" /> Subtitles;                        lang: <xsl:value-of select="/t:tt/@xml:lang" />;                        SAMI_Type: CC;                        /* anything below will override above */                        <xsl:apply-templates select="/t:tt/t:head/t:styling/t:style[@id=$default-style]/@tts:*" />                        }                    </xsl:comment>                </STYLE>            </HEAD>            <xsl:apply-templates select="/t:tt/t:body" />        </SAMI>    </xsl:template>    <xsl:template match="t:body">        <BODY>            <xsl:apply-templates />        </BODY>    </xsl:template>    <xsl:template match="t:body/t:div">        <xsl:apply-templates />    </xsl:template>    <xsl:template match="t:p">        <xsl:variable name="start" select="cjb:getMilliseconds(string(@begin))" />        <xsl:variable name="end" select="cjb:getMilliseconds(string(@end))" />        <xsl:variable name="following-start" select="cjb:getMilliseconds(string(following-sibling::t:p/@begin))" />        <Sync START="{$start}">            <p class="{$default-class}">                <xsl:if test="@style">                    <xsl:attribute name="class">                        <xsl:value-of select="$default-class" />                    </xsl:attribute>                </xsl:if>                <div>                    <xsl:if test="@style=$default-style and @tts:*">                        <xsl:attribute name="style">                            <xsl:apply-templates select="@tts:*" />                        </xsl:attribute>                    </xsl:if>                    <xsl:if test="@style!=$default-style">                        <xsl:attribute name="style">                            <xsl:apply-templates select="/t:tt/t:head/t:styling/t:style[@id=current()/@style]/@tts:*|@tts:*" />                        </xsl:attribute>                    </xsl:if>                    <xsl:apply-templates />                </div>            </p>        </Sync>        <xsl:if test="$end &lt; ($following-start - 10)">            <xsl:text>      </xsl:text>            <Sync START="{$end}">                <p>                    <nonbreakingspace />                </p>            </Sync>        </xsl:if>    </xsl:template>    <xsl:template match="@tts:*">        <xsl:value-of select="cjb:hyphenate(local-name())" />:<xsl:value-of select="." />;    </xsl:template>    <xsl:template match="*">        <xsl:element name="{local-name(.)}">            <xsl:if test="@tts:*">                <xsl:attribute name="style">                    <xsl:apply-templates select="@tts:*" />                </xsl:attribute>            </xsl:if>            <xsl:apply-templates select="@*[namespace-uri()!='http://www.w3.org/2006/10/ttaf1#style']" />            <xsl:apply-templates />        </xsl:element>    </xsl:template>    <xsl:template match="text()">        <xsl:copy />    </xsl:template>    <xsl:template match="@*">        <xsl:attribute name="{local-name()}">            <xsl:value-of select="." />        </xsl:attribute>    </xsl:template></xsl:stylesheet>'@    }    PROCESS{        if ($_ -is [xml]){            $result = (invoke-transform -inxml $_ -inxsl $xslt)            $result = $result.replace(" xmlns=`"http://www.w3.org/2006/10/ttaf1`"", "")            $result = $result.replace("<nonbreakingspace />", "&nbsp;")            $result        }        }    END{        if ($inxml -is [xml]){            $result = (invoke-transform -inxml $inxml -inxsl $xslt)            $result = $result.replace(" xmlns=`"http://www.w3.org/2006/10/ttaf1`"", "")            $result = $result.replace("<nonbreakingspace />", "&nbsp;")            $result        }    }} The replaces on the $result string look like a bit of a hack but they do need to be there. It can be used like this PS> . .\T-TtafToSmiM1.ps1 # dot source this filePS> T-TtafToSmi [xml](gc ttaf.xml) | sc -encoding ascii or PS> . .\T-TtafToSmiM1.ps1 # dot source this filePS> [xml](gc .\ttaf.xml) | T-TtafToSmi | sc -encoding ascii Here is the code T-TtafToSmiM1.zip (2.39 kb)
Here is a translet that will transform Timed Text (TT) Authoring Format 1.0  to SRT. function T-TtafToSrt{    param ($inxml)    BEGIN {        . pslib:\xml\invoke-transform.ps1        $xslt = New-Object Xml.Xmldocument        $xslt.PSBase.PreserveWhitespace = $true        $xslt.LoadXml(@' <xsl:stylesheetxmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:t="http://www.w3.org/2006/10/ttaf1"xmlns="http://www.w3.org/2006/10/ttaf1"xmlns:ttp="http://www.w3.org/2006/10/ttaf1#parameter"xmlns:tts="http://www.w3.org/2006/10/ttaf1#style"xmlns:ttm="http://www.w3.org/2006/10/ttaf1#metadata"version="1.0"><xsl:output indent="yes" method="text" /><xsl:template match="/t:tt"><xsl:apply-templates select="/t:tt/t:body" /></xsl:template><xsl:template match="t:div"><xsl:apply-templates select="t:p" /></xsl:template><xsl:template match="t:p"><xsl:text></xsl:text><xsl:value-of select="count(preceding-sibling::t:p)" /><xsl:text></xsl:text><xsl:value-of select="concat(substring-before(@begin, '.'), ',', substring-after(@begin, '.'))" />0 --&gt; <xsl:value-of select="concat(substring-before(@begin, '.'), ',', substring-after(@begin, '.'))" />0<xsl:text></xsl:text><xsl:apply-templates /></xsl:template><xsl:template match="t:br"><xsl:text></xsl:text></xsl:template><xsl:template match="node()|@*"><xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy></xsl:template></xsl:stylesheet>'@)        }    PROCESS{        if ($_ -is [xml]){            invoke-transform -inxml $_ -inxsl $xslt        }        }    END{        if ($xml -is [xml]){           invoke-transform -inxml $xml -inxsl $xslt        }    }} It can be used like this PS> . .\T-TtafToSrt.ps1 # dot source this filePS> T-TtafToSrt [xml(gc .\ttaf.xml) | sc -encoding ascii or PS> . .\T-TtafToSrt.ps1 # dot source this filePS> [xml](gc .\ttaf.xml) | T-TtafToSrt | sc -encoding ascii Note that if you aren't passing it on down the pipeline and are sending it to a file you have to set the encoding to ascii or applications will ignore it. Here is the code. T-TtafToSrt.zip (1.10 kb)
One would think this should be easy. Just put Powershell into the Language of an msxsl:script element. But it doesn't work :-D Pity.  I rooted around and the language attribute can be any of the CodeDomProviders available on the system. PS> [System.CodeDom.Compiler.CodeDomProvider]::GetAllCompilerInfo()CodeDomProviderType IsCodeDomProviderTypeValid------------------- --------------------------Microsoft.CSharp.CSharpCodeProvider TrueMicrosoft.VisualBasic.VBCodeProvider TrueMicrosoft.JScript.JScriptCodeProvider TrueMicrosoft.VisualC.CppCodeProvider True According to thisthere won't be one for Powershell soon. Never mind, I still want to use Powershell from Xslt. So I tried this. function T-AddChecksum{    param ($inxml)    begin{       . pslib:\xml\invoke-transform.ps1       [xml]$xslt = @'<?xml version="1.0" encoding="utf-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:cjb="cjb">  <xsl:template match="node()|@*">    <xsl:copy>      <xsl:apply-templates select="@*|node()"/>    </xsl:copy>  </xsl:template>  <xsl:template match="*[local-name()='file']">    <xsl:variable name="fname"><xsl:call-template name="get-path" /></xsl:variable>    <xsl:copy>      <xsl:apply-templates select="@*|node()"/>      <xsl:attribute name="Checksum"><xsl:value-of select="cjb:GetChecksum(string($fname))" /></xsl:attribute>    </xsl:copy>  </xsl:template>  <xsl:template name="get-path">    <xsl:for-each select="ancestor-or-self::*[not(@Root)]">      <xsl:value-of select="@Base" /><xsl:text>\</xsl:text><xsl:value-of select="@Name" />    </xsl:for-each>  </xsl:template></xsl:stylesheet>'@$func = @'    function GetChecksum([string]$file){        $stream = [System.IO.File]::OpenRead($file)        $sha256 = new-object System.Security.Cryptography.SHA256Managed        $checksum = $sha256.ComputeHash($stream)        [System.BitConverter]::ToString($checksum).Replace("-", [system.String]::Empty)    }'@       $pso = new-object psobject       $pso = add-member -inputobject $pso -membertype scriptmethod -name GetChecksum -value {$func} -PassThru    }    process{        if ($_ -is [xml]){            [xml](invoke-transform -inxml $_ -inxsl $xslt -extensionobjects @{"cjb"=$pso})        }    }    end{        if ($inxml -is [xml]){            [xml](invoke-transform -inxml $inxml -inxsl $xslt -extensionobjects @{"cjb"=$pso})        }    }} But that doesn't work. It is so long since I wrote it I can't remember if it worked, worked under version 1.0, worked in the CTP, seemed like a good idea that should work but didn't or it didn't work at all. Anyway it still looks like a good idea to me. This is the object created with C# in the previous example. PS > $cs|gmTypeName: ChecksumName MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetChecksum Method string GetChecksum(string file)GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() This is the Powershell object created with PSObject PS D:\powershell\temp> $pso|gmTypeName: System.Management.Automation.PSCustomObjectName MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj)GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() GetChecksum ScriptMethod System.Object GetChecksum(); So it is not a valid Xslt extension object? Right, so we can call C# from Xslt via an extension object and we can call Powershell from C# so all we have to do is write a C# wrapper around the Powershell script and we can call Powershell from Xslt. function T-AddChecksum{    param ($inxml)    begin{        . pslib:\xml\invoke-transform.ps1        [xml]$xslt = @'<?xml version="1.0" encoding="utf-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:cjb="cjb">  <xsl:template match="node()|@*">    <xsl:copy>      <xsl:apply-templates select="@*|node()"/>    </xsl:copy>  </xsl:template>  <xsl:template match="*[local-name()='file']">    <xsl:variable name="fname"><xsl:call-template name="get-path" /></xsl:variable>    <xsl:copy>      <xsl:apply-templates select="@*|node()"/>      <xsl:attribute name="Checksum"><xsl:value-of select="cjb:GetChecksum(string($fname))" /></xsl:attribute>    </xsl:copy>  </xsl:template>  <xsl:template name="get-path">    <xsl:for-each select="ancestor-or-self::*[not(@Root)]">      <xsl:value-of select="@Parent" /><xsl:text>\</xsl:text><xsl:value-of select="@Name" />    </xsl:for-each>  </xsl:template></xsl:stylesheet>'@        $code = @'using System;using System.Management.Automation;public partial class PSCaller{    ScriptBlock _script;    public PSCaller(ScriptBlock script) { Script = script; }    public ScriptBlock Script    {        get { return _script; }        set { _script = value; }    }    public String GetChecksum(String file)    {        try {            return (string)Script.Invoke(file)[0].BaseObject;        } catch(Exception ex) {            throw new InvalidOperationException("Script failed***!", ex);        }    }}'@       Add-Type -TypeDefinition $code       $psco = new-object PSCaller({            $file = $args[0]            $stream = [System.IO.File]::OpenRead($file)            $sha256 = new-object System.Security.Cryptography.SHA256Managed            $checksum = $sha256.ComputeHash($stream)            [System.BitConverter]::ToString($checksum).Replace("-", [system.String]::Empty)       })    }    process{        [xml](invoke-transform -inxml $_ -inxsl $xslt -extensionobjects @{"cjb"=$psco})    }    end{        [xml](invoke-transform -inxml $inxml -inxsl $xslt -extensionobjects @{"cjb"=$psco})    }} This works but it is a bit clunky. The name and signature 'String GetChecksum(String file)' is in the C# script and is seperate from the actual code that is in the Powershell script. A right mashup :-) What we need is a way of describing the signature and Powershell script in one place. I started looking at DSLs. Here is a simple class/method DSL $dsl = {    psclass public PSCaller{        method String GetChecksum([String]){            $file = $args[0]            $stream = [System.IO.File]::OpenRead($file)            $sha256 = new-object System.Security.Cryptography.SHA256Managed            $checksum = $sha256.ComputeHash($stream)            [System.BitConverter]::ToString($checksum).Replace("-", [system.String]::Empty)        }    }} I was using 'class' as the class keyword but it is now a Powershell reserved word so I am using 'psclass'. It can handle multiple methods with multiple arguments and the arguments can be any type i.e. [System.Xml.XmlElement]. This example code defines 1 method with the signature 'String GetChecksum([String])' followed by the script itself. It produces the following C# using System; using System.Collections; using System.Management.Automation; using System.Xml; public class PSCaller { ScriptBlock script0; public PSCaller(ScriptBlock Script0) { script0 = Script0; } public String GetChecksum(string p0) { ScriptBlock Script = script0; try { return (String)Script.Invoke(p0)[0].BaseObject; } catch (Exception ex) { throw new InvalidOperationException("Script failed***!", ex); } } } and an array of ScriptBlocks. It then compiles the code and passes the array of ScriptBlocks to the new object constructor. [string]$code = &$dslAdd-Type -TypeDefinition $code -ReferencedAssemblies System.xml$NewObject = new-object PSCaller($scriptArray) This is what the object looks like. PS > $newobject|gmTypeName: PSCaller2Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetChecksum Method string GetChecksum(string p0) GetDifferentChecksum Method string GetDifferentChecksum(string p0)GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() So this is what a Translet might look like function T-AddChecksum{    param ($inxml)    begin{       . pslib:\xml\invoke-transform.ps1       . pslib:\xml\Create-ObjectFromDSL.ps1        [xml]$xslt = @'<?xml version="1.0" encoding="utf-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:cjb="cjb">  <xsl:template match="node()|@*">    <xsl:copy>      <xsl:apply-templates select="@*|node()"/>    </xsl:copy>  </xsl:template>  <xsl:template match="*[local-name()='file']">    <xsl:variable name="fname"><xsl:call-template name="get-path" /></xsl:variable>    <xsl:copy>      <xsl:apply-templates select="@*|node()"/>      <xsl:attribute name="Checksum"><xsl:value-of select="cjb:GetChecksum(string($fname))" /></xsl:attribute>    </xsl:copy>  </xsl:template>  <xsl:template name="get-path">    <xsl:for-each select="ancestor-or-self::*[not(@Root)]">      <xsl:value-of select="@Parent" /><xsl:text>\</xsl:text><xsl:value-of select="@Name" />    </xsl:for-each>  </xsl:template></xsl:stylesheet>'@                $dsl = {            psclass public PSCaller{                method String GetChecksum([String]){                    $file = $args[0]                    $stream = [System.IO.File]::OpenRead($file)                    $sha256 = new-object System.Security.Cryptography.SHA256Managed                    $checksum = $sha256.ComputeHash($stream)                    [System.BitConverter]::ToString($checksum).Replace("-", [system.String]::Empty)                }                method String GetDifferentChecksum([String]){                    $file = $args[0] #different                    $stream = [System.IO.File]::OpenRead($file)                    $sha256 = new-object System.Security.Cryptography.SHA256Managed                    $checksum = $sha256.ComputeHash($stream)                    [System.BitConverter]::ToString($checksum).Replace("-", [system.String]::Empty)                }            }        }        $psco = Create-ObjectFromDSL $dsl    }    process{        if ($_ -is [xml]){            [xml](invoke-transform -inxml $_ -inxsl $xslt -extensionobjects @{"cjb"=$psco})        }    }    end{        if ($inxml -is [xml]){            [xml](invoke-transform -inxml $inxml -inxsl $xslt -extensionobjects @{"cjb"=$psco})        }    }} To use it you need the Create-ObjectFromDSL.ps1 script. Here it is Create-ObjectFromDSL.zip (953.00 bytes) Why you would want to do this and what you would use it for is up to you. Most people would use Java, JavaScript or C# to extend Xslt. In most cases I would generate Xml from the things that Powershell can do well and pass that into a transform. But this just shows that is is partly possible to use Powershell as an Xslt scripting language and it adds another arrow to your quiver. Since we have to do the Create-ObjectFromDSL step anyway we could change the Xslt to something like this <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:cjbxsl="urn:schemas-bayes-co-uk:xslt"xmlns:ucjb="urn:schemas-bayes-co-uk:extension-object" version="1.0">    <cjbxsl:script language="Powershell" implements-prefix="ucjb">        <cjbxsl:method name="GetChecksum" returns="string" args="string">            <![CDATA[                $file = $args[0]                $stream = [System.IO.File]::OpenRead($file)                $sha256 = new-object System.Security.Cryptography.SHA256Managed                $checksum = $sha256.ComputeHash($stream)                [System.BitConverter]::ToString($checksum).Replace("-", [system.String]::Empty)            ]]>        </cjbxsl:method>    </cjbxsl:script>    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="*[local-name()='file']">        <xsl:variable name="fname">            <xsl:call-template name="get-path" />        </xsl:variable>        <xsl:copy>            <xsl:apply-templates select="@*|node()" />            <xsl:attribute name="Checksum">                <xsl:value-of select="ucjb:GetChecksum(string($fname))" />            </xsl:attribute>        </xsl:copy>    </xsl:template>    <xsl:template name="get-path">        <xsl:for-each select="ancestor-or-self::*[not(@Root)]">            <xsl:value-of select="@Parent" />            <xsl:text>\</xsl:text>            <xsl:value-of select="@Name" />        </xsl:for-each>    </xsl:template></xsl:stylesheet> then we can pass the stylesheet through another translet to create the C# and the extension object :-D. The output from that translet would be the extension object that is actually passed as the extension object to the original stylesheet and used in the pipeline. Oh dizzy! function T-CreateObjectFromPSScriptXslt{    param ($inxml)    begin{        . pslib:\xml\invoke-transform.ps1        $xslt = New-Object Xml.Xmldocument        $xslt.PSBase.PreserveWhitespace = $true        $xslt.LoadXml(@' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:cjbxsl="urn:schemas-bayes-co-uk:xslt" version="1.0">    <xsl:output method="text" />    <xsl:template match="cjbxsl:script">$code=@"        using System;        using System.Collections;        using System.Management.Automation;        using System.Xml;        public class <xsl:value-of select="@assembly-name" />        {        <xsl:apply-templates select="cjbxsl:method" mode="declarations" />        public <xsl:value-of select="@assembly-name" />(        <xsl:apply-templates select="cjbxsl:method" mode="constructor-args" />        )        {        <xsl:apply-templates select="cjbxsl:method" mode="constructor-body" />        }        <xsl:apply-templates select="cjbxsl:method" mode="methods" />        }"@Add-Type -TypeDefinition $code -ReferencedAssemblies System.xml$NewObject = New-Object <xsl:value-of select="@assembly-name" />(<xsl:apply-templates select="cjbxsl:method" mode="new-object" />)$NewObject    </xsl:template>    <xsl:template match="cjbxsl:method" mode="declarations">        public ScriptBlock script<xsl:value-of select="count(preceding::cjbxsl:method)" />;    </xsl:template>    <xsl:template match="cjbxsl:method" mode="constructor-args">        ScriptBlock Script<xsl:value-of select="count(preceding::cjbxsl:method)" />        <xsl:if test="not(position()=last())">, </xsl:if>    </xsl:template>    <xsl:template match="cjbxsl:method" mode="constructor-body">        script<xsl:value-of select="count(preceding::cjbxsl:method)" /> = Script<xsl:value-of select="count(preceding::cjbxsl:method)" />;    </xsl:template>    <xsl:template match="cjbxsl:method" mode="methods">        public <xsl:value-of select="@returns" /> <xsl:text>          </xsl:text> <xsl:value-of select="@name" />(<xsl:value-of select="@args" />)        {        ScriptBlock Script = script<xsl:value-of select="count(preceding::cjbxsl:method)" />;        try        {        return (String)Script.Invoke(p0)[0].BaseObject;        }        catch (Exception ex)        {        throw new InvalidOperationException("Script failed***!", ex);        }        }    </xsl:template>    <xsl:template match="cjbxsl:method" mode="new-object">        {<xsl:value-of select="." />}<xsl:if test="not(position()=last())">,</xsl:if>    </xsl:template>    <xsl:template match="@* | node()">        <xsl:apply-templates select="@* | node()" />    </xsl:template></xsl:stylesheet>'@)    }    process{        if ($_ -is [xml]){            invoke-transform -inxml $_ -inxsl $xslt        }    }    end{        if ($inxml -is [xml]){            invoke-transform -inxml $inxml -inxsl $xslt        }    }} And here is a stylesheet that will use it function T-AddChecksum{    param ($inxml)    begin{        . pslib:\xml\xslt\T-CreateObjectFromPSScriptXslt.ps1        . pslib:\xml\invoke-transform.ps1        [xml]$xslt = @' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:cjbxsl="urn:schemas-bayes-co-uk:xslt" xmlns:ucjb="urn:cjb" version="1.0">    <cjbxsl:script language="Powershell" implements-prefix="ucjb" assembly-name="ChecksumSHA3">        <cjbxsl:method name="GetChecksum" returns="string" args="string p0">            <![CDATA[                $file = $args[0]                $stream = [System.IO.File]::OpenRead($file)                $sha256 = new-object System.Security.Cryptography.SHA256Managed                $checksum = $sha256.ComputeHash($stream)                [System.BitConverter]::ToString($checksum).Replace("-", [system.String]::Empty)            ]]>        </cjbxsl:method>        <cjbxsl:method name="GetChecksum384" returns="string" args="string p0">            <![CDATA[                $file = $args[0]                $stream = [System.IO.File]::OpenRead($file)                $sha384 = new-object System.Security.Cryptography.SHA384Managed                $checksum = $sha384.ComputeHash($stream)                [System.BitConverter]::ToString($checksum).Replace("-", [system.String]::Empty)            ]]>        </cjbxsl:method>    </cjbxsl:script>    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="*[local-name()='file']">        <xsl:variable name="fname">            <xsl:call-template name="get-path" />        </xsl:variable>        <xsl:copy>            <xsl:apply-templates select="@*|node()" />            <xsl:attribute name="Checksum">                <xsl:value-of select="ucjb:GetChecksum(string($fname))" />            </xsl:attribute>        </xsl:copy>    </xsl:template>    <xsl:template name="get-path">        <xsl:for-each select="ancestor-or-self::*[not(@Root)]">            <xsl:value-of select="@Parent" />            <xsl:text>\</xsl:text>            <xsl:value-of select="@Name" />        </xsl:for-each>    </xsl:template></xsl:stylesheet>'@        $psco = Invoke-Expression (T-CreateObjectFromPSScriptXslt $xslt)    }    process{        if ($_ -is [xml]){            [xml](invoke-transform -inxml $_ -inxsl $xslt -extensionObjects @{ucjb=$psco})        }    }    end{        if ($inxml -is [xml]){            [xml](invoke-transform -inxml $inxml -inxsl $xslt -extensionObjects @{ucjb=$psco})        }    }} Voila!!! Powershell as an xslt scripting language.
Here is a simple translet for finding duplicates. function T-FindDuplicates{    param ($inxml)    begin{        . PSlib:\xml\invoke-transform.ps1        [xml]$xslt = @" <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">    <xsl:output method="xml" indent="yes" />    <xsl:key name="file-checksums" match="file" use="@Checksum" />    <xsl:template match="file">        <xsl:copy>            <xsl:attribute name="Duplicate">                <xsl:value-of select="count(key('file-checksums', @Checksum)) &gt; 1" />            </xsl:attribute>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="@* | node()">        <xsl:copy>            <xsl:apply-templates select="@* | node()" />        </xsl:copy>    </xsl:template></xsl:stylesheet>"@    }    process{        if ($_ -is [xml]){            [xml](invoke-transform -inxml $_ -inxsl $xslt)        }    }    end{        if ($inxml -is [xml]){           [xml](invoke-transform -inxml $inxml -inxsl $xslt)        }    }} As you can see in the code it adds a Duplicate attribute with a value of true or false depending on whether there is a file with a duplicate @Checksum. It can be used like this PS> . .\T-GetDirAsXml.ps1 PS> . .\T-AddChecksum.ps1 PS> . .\T-FindDuplicates.ps1 PS> Get-DirAsXml | T-AddChecksum | T-FindDuplicates It might produce <root Name="root" Root="True" Date="2008/11/03 01:35:14">    <folder Name="test" Base="D:\powershell\blog\test" Parent="D:\powershell\blog">        <folder Name="test2">            <file Duplicate="true" Name="test.ps1" Checksum="C47313D06C6AADA288AF6D61E03EFD7FA7C52DD73AB097E9D556535D330798D3" />            <file Duplicate="false" Name="test.txt" Checksum="CE217706948A41613FFA00C46B64D48A514D3D80758C8334EE00D6B0786AE47F" />            <file Duplicate="true" Name="test.zip" Checksum="7F2CCA02F17FF0E9458C0777C659D6D00B80F1C9D2921AEC971AE9A82D296AA5" />            <file Duplicate="true" Name="tmp.xml" Checksum="1351245F9834D0406C42DD5AF622FCA691A9A36F440A7C88F389927800292303" />        </folder>        <file Duplicate="true" Name="test.ps1" Checksum="C47313D06C6AADA288AF6D61E03EFD7FA7C52DD73AB097E9D556535D330798D3" />        <file Duplicate="false" Name="test.txt" Checksum="0D7439F5894B4E8EFEC8FB409635D0D8EA7A450E902F6B30B335907B5867DF16" />        <file Duplicate="true" Name="test.zip" Checksum="7F2CCA02F17FF0E9458C0777C659D6D00B80F1C9D2921AEC971AE9A82D296AA5" />        <file Duplicate="true" Name="tmp.xml" Checksum="1351245F9834D0406C42DD5AF622FCA691A9A36F440A7C88F389927800292303" />    </folder></root> Here is the code T-FindDuplicates.zip (745 b) All of the files in folder test2 are copies of the files in test except for test.txt and as you can see only having an @Duplicate indicator doesn't tell you which file the file is a duplicate of so this translet is only useful if you have very few duplicate files. What you do when you find a duplicate is up to you and depends very much on the downstream application. One thing you could do is put a list of the duplicate files into an attribute like this <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">    <xsl:output method="xml" indent="yes" />    <xsl:key name="file-checksums" match="file" use="@Checksum" />    <xsl:template match="file">        <xsl:copy>            <xsl:if test="count(key('file-checksums', @Checksum)) &gt; 1">                <xsl:attribute name="Duplicate">true</xsl:attribute>                <xsl:attribute name="Duplicates">                    <xsl:for-each select="key('file-checksums', @Checksum)">                        <xsl:call-template name="get-path" />                        <xsl:value-of select="'&#xA;'" />                    </xsl:for-each>                </xsl:attribute>            </xsl:if>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template name="get-path">        <xsl:for-each select="ancestor-or-self::*[not(@Root)]">            <xsl:value-of select="@Parent" />            <xsl:text>\</xsl:text>            <xsl:value-of select="@Name" />        </xsl:for-each>    </xsl:template>    <xsl:template match="@* | node()">        <xsl:copy>            <xsl:apply-templates select="@* | node()" />        </xsl:copy>    </xsl:template></xsl:stylesheet> Which will produce <root Name="root" Root="True" Date="2008/11/03 01:35:14">    <folder Name="test" Base="D:\powershell\blog\test" Parent="D:\powershell\blog">        <folder Name="test2">            <file Duplicate="true" Duplicates="D:\powershell\blog\test\test2\test.ps1 D:\powershell\blog\test\test.ps1" Name="test.ps1" Checksum="C47313D06C6AADA288AF6D61E03EFD7FA7C52DD73AB097E9D556535D330798D3" />            <file Name="test.txt" Checksum="CE217706948A41613FFA00C46B64D48A514D3D80758C8334EE00D6B0786AE47F" />            <file Duplicate="true" Duplicates="D:\powershell\blog\test\test2\test.zip D:\powershell\blog\test\test.zip" Name="test.zip" Checksum="7F2CCA02F17FF0E9458C0777C659D6D00B80F1C9D2921AEC971AE9A82D296AA5" />            <file Duplicate="true" Duplicates="D:\powershell\blog\test\test2\tmp.xml D:\powershell\blog\test\tmp.xml" Name="tmp.xml" Checksum="1351245F9834D0406C42DD5AF622FCA691A9A36F440A7C88F389927800292303" />        </folder>        <file Duplicate="true" Duplicates="D:\powershell\blog\test\test2\test.ps1 D:\powershell\blog\test\test.ps1" Name="test.ps1" Checksum="C47313D06C6AADA288AF6D61E03EFD7FA7C52DD73AB097E9D556535D330798D3" />        <file Name="test.txt" Checksum="0D7439F5894B4E8EFEC8FB409635D0D8EA7A450E902F6B30B335907B5867DF16" />        <file Duplicate="true" Duplicates="D:\powershell\blog\test\test2\test.zip D:\powershell\blog\test\test.zip" Name="test.zip" Checksum="7F2CCA02F17FF0E9458C0777C659D6D00B80F1C9D2921AEC971AE9A82D296AA5" />        <file Duplicate="true" Duplicates="D:\powershell\blog\test\test2\tmp.xml D:\powershell\blog\test\tmp.xml" Name="tmp.xml" Checksum="1351245F9834D0406C42DD5AF622FCA691A9A36F440A7C88F389927800292303" />    </folder></root> Here is the code T-FindDuplicatesInfoTip.zip (927 b)
Another way to extend Xslt is to pass an extension object into the transform.  function T-AddChecksum{    param ($inxml)    begin{        . pslib:\xml\invoke-transform.ps1    [xml]$xslt = @' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"    xmlns:cjb="cjb" version="1.0">    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="*[local-name()='file']">        <xsl:variable name="fname">            <xsl:call-template name="get-path" />        </xsl:variable>        <xsl:copy>            <xsl:apply-templates select="@*|node()" />            <xsl:attribute name="Checksum">                <xsl:value-of select="cjb:GetChecksum(string($fname))" />            </xsl:attribute>        </xsl:copy>    </xsl:template>    <xsl:template name="get-path">        <xsl:for-each select="ancestor-or-self::*[not(@Root)]">            <xsl:value-of select="@Parent" />            <xsl:text>\</xsl:text>            <xsl:value-of select="@Name" />        </xsl:for-each>    </xsl:template></xsl:stylesheet>'@$code = @'public class Checksum{    public System.String GetChecksum(System.String file) {        using (System.IO.FileStream stream = System.IO.File.OpenRead(file))        {            System.Security.Cryptography.SHA256Managed sha = new System.Security.Cryptography.SHA256Managed();            byte[] checksum = sha.ComputeHash(stream);            return System.BitConverter.ToString(checksum).Replace("-", System.String.Empty);        }   }}'@        Add-Type -TypeDefinition $code        $cs = new-object Checksum    }    process{        if ($_ -is [xml]){            [xml](invoke-transform -inxml $_ -inxsl $xslt -extensionobjects @{"cjb"=$cs})        }    }    end{        if ($inxml -is [xml]){            [xml](invoke-transform -inxml $inxml -inxsl $xslt -extensionobjects @{"cjb"=$cs})        }    }} It can be used the same way as before PS> . .\T-AddChecksum.ps1 #dot source transletPS> . .\Get-DirAsXml.ps1 #dot source Get-DirAsXml PS> Get-DirAsXml D:\powershell\test -props @{Length=""} | T-AddChecksum Here is the code T-AddChecksumCObject.zip (1.08 kb)
This translet will add a Ratio attribute to a daxml file. It is very useful to find out where all of the space is taken up in folders and if your downstream application is SVG or WPF then Ratio can be used in a lot of places. function T-AddRatio{    param ($inxml)    begin{        . PSlib:\xml\invoke-transform.ps1        [xml]$xslt = @" <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="folder|file">        <xsl:copy>            <xsl:if test="parent::*/@Length">                <xsl:attribute name="Ratio">                    <xsl:value-of select="@Length div parent::*/@Length" />                </xsl:attribute>            </xsl:if>            <xsl:if test="@Base">                <xsl:attribute name="Ratio">1</xsl:attribute>            </xsl:if>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template></xsl:stylesheet>"@    }    process{        if ($_ -is [xml]){            [xml](invoke-transform -inxml $_ -inxsl $xslt)        }    }    end{        if ($inxml -is [xml]){           [xml](invoke-transform -inxml $inxml -inxsl $xslt)        }    }} As you can see in the code it checks that the folder node has a @Length attribute. Length property is not added to folders by Get-DirAsXml so first use the T-DirLength translet to add a @Length attribute. It can be used like this PS> . .\Get-DirAsXml PS> . .\T-DirLength PS> . .\T-AddRatio PS> Get-DirAsXml .\test -props @{Length=""}|T-DirLength|T-AddRatio It might produce this <root Name="root" Root="True" Date="2008/12/03 11:15:51">    <folder Ratio="1" Length="19678" Name="test" Base="D:\powershell\blog\test" Parent="D:\powershell\blog">        <folder Ratio="0.5" Length="9839" Name="test2">            <file Ratio="0.3384490293729038" Name="test.ps1" Length="3330" />            <file Ratio="0.08496798455127553" Name="test.txt" Length="836" />            <file Ratio="0.1311108852525663" Name="test.zip" Length="1290" />            <file Ratio="0.4454721008232544" Name="tmp.xml" Length="4383" />        </folder>        <file Ratio="0.1692245146864519" Name="test.ps1" Length="3330" />        <file Ratio="0.04248399227563777" Name="test.txt" Length="836" />        <file Ratio="0.06555544262628315" Name="test.zip" Length="1290" />        <file Ratio="0.2227360504116272" Name="tmp.xml" Length="4383" />    </folder></root> Here is the code T-AddRatio.zip (671 b)
Here are ways to write translets that will do more than the just an Xslt transform. You might want to add an MD5 or SHA256 checksum to all the files within a tree. Xslt doesn't do this natively so you need to extend it. Here is one way it uses C# as the Xslt scripting language function T-AddChecksum{    param ($inxml)    begin{        . .\invoke-transform.ps1       [xml]$xslt = @' <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:ucjb="urn:cjb" version="1.0">    <msxsl:script language="C#" implements-prefix="ucjb"> public string GetChecksum(String file) { using (System.IO.FileStream stream = System.IO.File.OpenRead(file)) { System.Security.Cryptography.SHA256Managed sha = new System.Security.Cryptography.SHA256Managed(); byte[] checksum = sha.ComputeHash(stream); return System.BitConverter.ToString(checksum).Replace("-", System.String.Empty); } }    </msxsl:script>    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="*[local-name()='file']">        <xsl:variable name="fname">            <xsl:call-template name="get-path" />        </xsl:variable>        <xsl:copy>            <xsl:apply-templates select="@*|node()" />            <xsl:attribute name="Checksum">                <xsl:value-of select="ucjb:GetChecksum(string($fname))" />            </xsl:attribute>        </xsl:copy>    </xsl:template>    <xsl:template name="get-path">        <xsl:for-each select="ancestor-or-self::*[not(@Root)]">            <xsl:value-of select="@Parent" />            <xsl:text>\</xsl:text>            <xsl:value-of select="@Name" />        </xsl:for-each>    </xsl:template></xsl:stylesheet>'@    }    process{        if ($_ -is [xml]){            [xml](invoke-transform -inxml $_ -inxsl $xslt)        }    }    end{        if ($inxml -is [xml]){            [xml](invoke-transform -inxml $inxml -inxsl $xslt)        }    }} It can be used like this PS> . .\T-AddChecksum.ps1 #dot source transletPS> . .\Get-DirAsXml.ps1 #dot source Get-DirAsXml PS> Get-DirAsXml D:\powershell\test -props @{Length=""} | T-AddChecksum or PS> T-AddChecksum [xml](gc .\tmp.xml ) and might produce <root Name="root" Root="True" Date="2009/11/03 05:45:37">    <folder Name="test" Base="D:\powershell\test">        <file Name="test.txt" Length="836" Checksum="0D7439F5894B4E8EFEC8FB409635D0D8EA7A450E902F6B30B335907B5867DF16" />        <file Name="test.ps1" Length="3330" Checksum="C47313D06C6AADA288AF6D61E03EFD7FA7C52DD73AB097E9D556535D330798D3" />        <file Name="test.zip" Length="1290" Checksum="7F2CCA02F17FF0E9458C0777C659D6D00B80F1C9D2921AEC971AE9A82D296AA5" />        <file Name="tmp.xml" Length="4383" Checksum="1351245F9834D0406C42DD5AF622FCA691A9A36F440A7C88F389927800292303" />    </folder></root> Here is the code T-AddChecksumCScript.zip (1.03 kb)
These are small Xslt transforms that modify some Xml in the powershell pipeline. Here is a simple one. It adds a Length attribute to daxml folder nodes. function T-DirLength{    param ($inxml)    begin{        . PSlib:\xml\invoke-transform.ps1        [xml]$xslt = @" <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">    <xsl:template match="node()|@*">        <xsl:copy>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template>    <xsl:template match="folder">        <xsl:copy>            <xsl:attribute name="Length">                <xsl:value-of select="sum(.//file/@Length)" />            </xsl:attribute>            <xsl:apply-templates select="@*|node()" />        </xsl:copy>    </xsl:template></xsl:stylesheet>"@    process{        if ($_ -is [xml]){            [xml](invoke-transform -inxml $_ -inxsl $xslt)        }    }    end{        if ($inxml -is [xml]){           [xml](invoke-transform -inxml $inxml -inxsl $xslt)        }    }} It does an Xslt identity transform on the Xml except for the 'folder' nodes to which it adds a Length attribute which is the sum of all the file/@Length attributes below the folder. Folders do not have a Length property so do not get a Length attribute even if you specify -props @{Length=""}. You could write a get-dirasxml custom props script to do this but getting that value at shell level is slow. This is much faster. It can be used like this PS> . .\T-DirLength.ps1 #dot source the translet filePS> . .\Get-DirAsXml.ps1 #dot source the Get-DirAsXml filePS> Get-DirAsXml D:\powershell\test -props @{Length=""} | T-DirLength and might produce <root Name="root" Root="True" Date="2008/11/03 09:55:40">    <folder Length="9839" Name="test" Base="D:\powershell\test">        <file Name="test.txt" Length="836" />        <file Name="test.ps1" Length="3330" />        <file Name="test.zip" Length="1290" />        <file Name="tmp.xml" Length="4383" />    </folder></root> Here is the code T-DirLength.zip (635 b)