Calendar

<<  April 2017  >>
MonTueWedThuFriSatSun
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

View posts in large calendar

RecentComments

None

 
 
     
 
There is no reason why a Translet shouldn't use XQuery as opposed to Xslt. So here is one that will transform a DAXml into a Windows Play List (WPL) using XQuery. It is quite simple function T-DAXmlToWpl{     param ($inxml)     begin{         Add-Type -path "PSLib:\xml\xquery\querymachine\QueryMachine.XQuery.dll"         $xq = @' (:<?wpl version="1.0"?>:) <smil>     <head>         <meta name="Generator" content="Powershell Translet T-Dax2Wpl"/>         <author>Chris Bayes</author>         <title>Playlist</title>     </head>     <body>         <seq>         { for $f in //file return              <media src="{$f/@FullName}" />         }         </seq>     </body> </smil> '@     }     process{         if ($_ -is [xml]){             $xqresult = [DataEngine.XQuery.XPathFactory]::QuerySingleNode($_, $xq)             $xqresult.OuterXml         }     }     end{         if ($inxml -is [xml]){             $xqresult = [DataEngine.XQuery.XPathFactory]::QuerySingleNode($inxml, $xq)             $xqresult.OuterXml         }     } } I am using OuterXml here because the result of the query is a single XmlElement (<smil>). The result of this translet would usually be written to a file with the Set-Content commandlet so it is ok to pass a string down the pipeline. If an [xml] object is required then [xml]($xqresult.OuterXml) can be used instead. I will have to do some timings to see if importing $xqresult into a new emty [xml] object is quicker than casting to an [xml] object. It can be used like this PS> . .\T-DAXmlToWpl.ps1 PS> Get-DirAsXml ..\test -props=@{FullName=""} | T-DAXmlToWpl | sc playlist.wpl After a bit of testing I changed the XQuery to     $xq = @' (:<?wpl version="1.0"?>:) document{ <smil>     <head>         <meta name="Generator" content="Powershell Translet T-Dax2Wpl"/>         <author>Chris Bayes</author>         <title>Playlist</title>     </head>     <body>         <seq>         { for $f in //file return             <media src="{$f/@FullName}" />         }         </seq>     </body> </smil> } '@ and QuerySingleNode returns an XmlDocument :-) however it has a <?xml version="1.0" encoding="utf-8"?> xml declaration which is not part of the SMIL spec but Windows Media Player doesn't seem to mind. I then tried function T-DAXmlToWpl{     param ($inxml)     begin{         Add-Type -path "PSLib:\xml\xquery\querymachine\QueryMachine.XQuery.dll"         $xq = @' document{     <?wpl version="1.0"?>,<smil>         <head>             <meta name="Generator" content="Powershell Translet T-DAXmlToWpl"/>             <author>Chris Bayes</author>             <title>Playlist</title>         </head>         <body>             <seq>             {                 for $f in //file[@Extension=".mp3"]                 return                   <media src="{$f/@FullName}" />             }             </seq>         </body>     </smil> } '@     }     process{         if ($_ -is [xml]){             [DataEngine.XQuery.XPathFactory]::QuerySingleNode($_, $xq)         }     }     end{         if ($inxml -is [xml]){             [DataEngine.XQuery.XPathFactory]::QuerySingleNode($inxml, $xq)         }     } } which produces something like this <?xml version="1.0" encoding="utf-8"?> <?wpl version="1.0"?> <smil>     <head>         <meta name="Generator" content="Powershell Translet T-DAXmlToWpl" />         <author>Chris Bayes</author>         <title>Playlist</title>     </head>     <body>         <seq>             <media src="D:\music\Culture - Two Sevens Clash (30th Anniversary Ed.) TQMP\04_Culture - Two Sevens Clash.mp3" />             ...         </seq>     </body> </smil> which has the xml declaration and the wpl processing instruction which Windows Media Player also plays so I will leave it like that. Because we are producing an XmlDocument we need to use the Format-Xml commandlet before we can use the Set-Content commandlet, something like this PS> Get-DirAsXml D:\music -props @{FullName="";Extension=""} | T-DAXmlToWpl | Format-Xml | Set-Content allplaylist.wpl
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)