PoshCode Logo PowerShell Code Repository

Xml Module 4.3 (modification of post by Joel Bennett view diff)
View followups from Joel Bennett | diff | embed code: <script type="text/javascript" src="http://PoshCode.org/embed/1682"></script>download | new post

My rewritten XML DSL now has better element name handling. See Blog Comments

New-XDocument no longer requires the “xe” command as long as the name of your XML Element doesn’t coincide with that of a PowerShell command (if it does, you need the “xe” on the front), and namespaces can be referred to by short name like dc:creator to keep things simple. Please review the examples on New-XDocument as this is a breaking change.

h4. The other functions round out the set of XML functionality (especially if you don’t have PSCX).

In particular, my Select-XML improves over the built-in Select-XML by leveraging Remove-XmlNamespace to provide a -RemoveNamespace parameter — if it’s supplied, all of the namespace declarations and prefixes are removed from all XML nodes (by an XSL transform) before searching (so you can actually find things, even with namespace-qualified xml). It is important to note that this means that the returned results will not have namespaces in them, even if the input XML did.

Also, only raw XmlNodes are returned from Select-Xml, so the output isn’t quite compatible with the built in Select-Xml — instead, it’s equivalent to using it the way I usually do: Select-Xml ... | Select-Object -Expand Node

New: Format-XML handles paths too :)

  1. #requires -version 2.0
  2.  
  3. # Improves over the built-in Select-XML by leveraging Remove-XmlNamespace http`://poshcode.org/1492
  4. # to provide a -RemoveNamespace parameter -- if it's supplied, all of the namespace declarations
  5. # and prefixes are removed from all XML nodes (by an XSL transform) before searching.
  6. # IMPORTANT: returned results *will not* have namespaces in them, even if the input XML did.
  7.  
  8. # Also, only raw XmlNodes are returned from this function, so the output isn't completely compatible
  9. # with the built in Select-Xml. It's equivalent to using Select-Xml ... | Select-Object -Expand Node
  10.  
  11. # Version History:
  12. # Select-Xml 2.0 This was the first script version I wrote.
  13. #                it didn't function identically to the built-in Select-Xml with regards to parameter parsing
  14. # Select-Xml 2.1 Matched the built-in Select-Xml parameter sets, it's now a drop-in replacement
  15. #                BUT only if you were using the original with: Select-Xml ... | Select-Object -Expand Node
  16. # Select-Xml 2.2 Fixes a bug in the -Content parameterset where -RemoveNamespace was *presumed*
  17. # Version    3.0 Added New-XDocument and associated generation functions for my XML DSL
  18. # Version    3.1 Fixed a really ugly bug in New-XDocument in 3.0 which I should not have released
  19. # Version    4.0 Never content to leave well enough alone, I've completely reworked New-XDocument
  20. # Version    4.1 Tweaked namespaces again so they don't cascade down when they shouldn't. Got rid of the unnecessary stack.
  21. # Version    4.2 Tightened xml: only cmdlet, function, and external scripts, with "-" in their names are exempted from being converted into xml tags.
  22. #                Fixed some alias error messages caused when PSCX is already loaded (we overwrite their aliases for cvxml and fxml)
  23. # Version    4.3 Added a Path parameter set to Format-XML so you can specify xml files for prety printing
  24.  
  25. $xlr8r = [type]::gettype("System.Management.Automation.TypeAccelerators")
  26. $xlinq = [Reflection.Assembly]::Load("System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
  27. $xlinq.GetTypes() | ? { $_.IsPublic -and !$_.IsSerializable -and $_.Name -ne "Extensions" -and !$xlr8r::Get[$_.Name] } | % {
  28.   $xlr8r::Add( $_.Name, $_.FullName )
  29. }
  30. if(!$xlr8r::Get["Stack"]) {
  31.    $xlr8r::Add( "Stack", "System.Collections.Generic.Stack``1, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" )
  32. }
  33. if(!$xlr8r::Get["Dictionary"]) {
  34.    $xlr8r::Add( "Dictionary", "System.Collections.Generic.Dictionary``2, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" )
  35. }
  36. if(!$xlr8r::Get["PSParser"]) {
  37.    $xlr8r::Add( "PSParser", "System.Management.Automation.PSParser, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" )
  38. }
  39.  
  40.  
  41.  
  42. filter Format-XML {
  43. #.Synopsis
  44. #   Pretty-print formatted XML source
  45. #.Description
  46. #   Runs an XmlDocument through an auto-indenting XmlWriter
  47. #.Parameter Xml
  48. #   The Xml Document
  49. #.Parameter Path
  50. #   The path to an xml document (on disc or any other content provider).
  51. #.Parameter Indent
  52. #   The indent level (defaults to 2 spaces)
  53. #.Example
  54. #   [xml]$xml = get-content Data.xml
  55. #   C:\PS>Format-Xml $xml
  56. #.Example
  57. #   get-content Data.xml | Format-Xml
  58. #.Example
  59. #   Format-Xml C:\PS\Data.xml
  60. #.Example
  61. #   ls *.xml | Format-Xml
  62. #
  63. Param(
  64.    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Document")]
  65.    [xml]$Xml
  66. ,
  67.    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="File")]
  68.    [Alias("PsPath")]
  69.    [string]$Path
  70. ,
  71.    [Parameter(Mandatory=$false)]
  72.    $Indent=2
  73. )
  74.    ## Load from file, if necessary
  75.    if($Path) { [xml]$xml = Get-Content $Path }
  76.    
  77.    $StringWriter = New-Object System.IO.StringWriter
  78.    $XmlWriter = New-Object System.Xml.XmlTextWriter $StringWriter
  79.    $xmlWriter.Formatting = "indented"
  80.    $xmlWriter.Indentation = $Indent
  81.    $xml.WriteContentTo($XmlWriter)
  82.    $XmlWriter.Flush()
  83.    $StringWriter.Flush()
  84.    Write-Output $StringWriter.ToString()
  85. }
  86. Set-Alias fxml Format-Xml -EA 0
  87.  
  88. function Select-Xml {
  89. #.Synopsis
  90. #  The Select-XML cmdlet lets you use XPath queries to search for text in XML strings and documents. Enter an XPath query, and use the Content, Path, or Xml parameter to specify the XML to be searched.
  91. #.Description
  92. #  Improves over the built-in Select-XML by leveraging Remove-XmlNamespace to provide a -RemoveNamespace parameter -- if it's supplied, all of the namespace declarations and prefixes are removed from all XML nodes (by an XSL transform) before searching.  
  93. #  
  94. #  However, only raw XmlNodes are returned from this function, so the output isn't currently compatible with the built in Select-Xml, but is equivalent to using Select-Xml ... | Select-Object -Expand Node
  95. #
  96. #  Also note that if the -RemoveNamespace switch is supplied the returned results *will not* have namespaces in them, even if the input XML did, and entities get expanded automatically.
  97. #.Parameter Content
  98. #  Specifies a string that contains the XML to search. You can also pipe strings to Select-XML.
  99. #.Parameter Namespace
  100. #   Specifies a hash table of the namespaces used in the XML. Use the format @{<namespaceName> = <namespaceUri>}.
  101. #.Parameter Path
  102. #   Specifies the path and file names of the XML files to search.  Wildcards are permitted.
  103. #.Parameter Xml
  104. #  Specifies one or more XML nodes to search.
  105. #.Parameter XPath
  106. #  Specifies an XPath search query. The query language is case-sensitive. This parameter is required.
  107. #.Parameter RemoveNamespace
  108. #  Allows the execution of XPath queries without namespace qualifiers.
  109. #  
  110. #  If you specify the -RemoveNamespace switch, all namespace declarations and prefixes are actually removed from the Xml before the XPath search query is evaluated, and your XPath query should therefore NOT contain any namespace prefixes.
  111. #
  112. #  Note that this means that the returned results *will not* have namespaces in them, even if the input XML did, and entities get expanded automatically.
  113. [CmdletBinding(DefaultParameterSetName="Xml")]
  114. PARAM(
  115.    [Parameter(Position=1,ParameterSetName="Path",Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
  116.    [ValidateNotNullOrEmpty()]
  117.    [Alias("PSPath")]
  118.    [String[]]$Path
  119. ,
  120.    [Parameter(Position=1,ParameterSetName="Xml",Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
  121.    [ValidateNotNullOrEmpty()]
  122.    [Alias("Node")]
  123.    [System.Xml.XmlNode[]]$Xml
  124. ,
  125.    [Parameter(ParameterSetName="Content",Mandatory=$true,ValueFromPipeline=$true)]
  126.    [ValidateNotNullOrEmpty()]
  127.    [String[]]$Content
  128. ,
  129.    [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)]
  130.    [ValidateNotNullOrEmpty()]
  131.    [Alias("Query")]
  132.    [String[]]$XPath
  133. ,
  134.    [Parameter(Mandatory=$false)]
  135.    [ValidateNotNullOrEmpty()]
  136.    [Hashtable]$Namespace
  137. ,
  138.    [Switch]$RemoveNamespace
  139. )
  140. BEGIN {
  141.    function Select-Node {
  142.    PARAM([Xml.XmlNode]$Xml, [String[]]$XPath, $NamespaceManager)
  143.    BEGIN {
  144.       foreach($node in $xml) {
  145.          if($NamespaceManager -is [Hashtable]) {
  146.             $nsManager = new-object System.Xml.XmlNamespaceManager $node.NameTable
  147.             foreach($ns in $Namespace.GetEnumerator()) {
  148.                $nsManager.AddNamespace( $ns.Key, $ns.Value )
  149.             }
  150.          }
  151.          foreach($path in $xpath) {
  152.             $node.SelectNodes($path, $NamespaceManager)
  153.    }  }  }  }
  154.  
  155.    [Text.StringBuilder]$XmlContent = [String]::Empty
  156. }
  157.  
  158. PROCESS {
  159.    $NSM = $Null; if($PSBoundParameters.ContainsKey("Namespace")) { $NSM = $Namespace }
  160.  
  161.    switch($PSCmdlet.ParameterSetName) {
  162.       "Content" {
  163.          $null = $XmlContent.AppendLine( $Content -Join "`n" )
  164.       }
  165.       "Path" {
  166.          foreach($file in Get-ChildItem $Path) {
  167.             [Xml]$Xml = Get-Content $file
  168.             if($RemoveNamespace) {
  169.                $Xml = Remove-XmlNamespace $Xml
  170.             }
  171.             Select-Node $Xml $XPath  $NSM
  172.          }
  173.       }
  174.       "Xml" {
  175.          foreach($node in $Xml) {
  176.             if($RemoveNamespace) {
  177.                $node = Remove-XmlNamespace $node
  178.             }
  179.             Select-Node $node $XPath $NSM
  180.          }
  181.       }
  182.    }
  183. }
  184. END {
  185.    if($PSCmdlet.ParameterSetName -eq "Content") {
  186.       [Xml]$Xml = $XmlContent.ToString()
  187.       if($RemoveNamespace) {
  188.          $Xml = Remove-XmlNamespace $Xml
  189.       }
  190.       Select-Node $Xml $XPath  $NSM
  191.    }
  192. }
  193.  
  194. }
  195. Set-Alias slxml Select-Xml -EA 0
  196.  
  197. function Convert-Node {
  198. #.Synopsis
  199. # Convert a single XML Node via XSL stylesheets
  200. param(
  201. [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
  202. [System.Xml.XmlReader]$XmlReader,
  203. [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$false)]
  204. [System.Xml.Xsl.XslCompiledTransform]$StyleSheet
  205. )
  206. PROCESS {
  207.    $output = New-Object IO.StringWriter
  208.    $StyleSheet.Transform( $XmlReader, $null, $output )
  209.    Write-Output $output.ToString()
  210. }
  211. }
  212.    
  213. function Convert-Xml {
  214. #.Synopsis
  215. #  The Convert-XML function lets you use Xslt to transform XML strings and documents.
  216. #.Description
  217. #.Parameter Content
  218. #  Specifies a string that contains the XML to search. You can also pipe strings to Select-XML.
  219. #.Parameter Namespace
  220. #   Specifies a hash table of the namespaces used in the XML. Use the format @{<namespaceName> = <namespaceUri>}.
  221. #.Parameter Path
  222. #   Specifies the path and file names of the XML files to search.  Wildcards are permitted.
  223. #.Parameter Xml
  224. #  Specifies one or more XML nodes to search.
  225. #.Parameter Xsl
  226. #  Specifies an Xml StyleSheet to transform with...
  227. [CmdletBinding(DefaultParameterSetName="Xml")]
  228. PARAM(
  229.    [Parameter(Position=1,ParameterSetName="Path",Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
  230.    [ValidateNotNullOrEmpty()]
  231.    [Alias("PSPath")]
  232.    [String[]]$Path
  233. ,
  234.    [Parameter(Position=1,ParameterSetName="Xml",Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
  235.    [ValidateNotNullOrEmpty()]
  236.    [Alias("Node")]
  237.    [System.Xml.XmlNode[]]$Xml
  238. ,
  239.    [Parameter(ParameterSetName="Content",Mandatory=$true,ValueFromPipeline=$true)]
  240.    [ValidateNotNullOrEmpty()]
  241.    [String[]]$Content
  242. ,
  243.    [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)]
  244.    [ValidateNotNullOrEmpty()]
  245.    [Alias("StyleSheet")]
  246.    [String[]]$Xslt
  247. )
  248. BEGIN {
  249.    $StyleSheet = New-Object System.Xml.Xsl.XslCompiledTransform
  250.    if(Test-Path @($Xslt)[0] -EA 0) {
  251.       Write-Verbose "Loading Stylesheet from $(Resolve-Path @($Xslt)[0])"
  252.       $StyleSheet.Load( (Resolve-Path @($Xslt)[0]) )
  253.    } else {
  254.       Write-Verbose "$Xslt"
  255.       $StyleSheet.Load(([System.Xml.XmlReader]::Create((New-Object System.IO.StringReader ($Xslt -join "`n")))))
  256.    }
  257.    [Text.StringBuilder]$XmlContent = [String]::Empty
  258. }
  259. PROCESS {
  260.    switch($PSCmdlet.ParameterSetName) {
  261.       "Content" {
  262.          $null = $XmlContent.AppendLine( $Content -Join "`n" )
  263.       }
  264.       "Path" {
  265.          foreach($file in Get-ChildItem $Path) {
  266.             Convert-Node -Xml ([System.Xml.XmlReader]::Create((Resolve-Path $file))) $StyleSheet
  267.          }
  268.       }
  269.       "Xml" {
  270.          foreach($node in $Xml) {
  271.             Convert-Node -Xml (New-Object Xml.XmlNodeReader $node) $StyleSheet
  272.          }
  273.       }
  274.    }
  275. }
  276. END {
  277.    if($PSCmdlet.ParameterSetName -eq "Content") {
  278.       [Xml]$Xml = $XmlContent.ToString()
  279.       Convert-Node -Xml $Xml $StyleSheet
  280.    }
  281. }
  282. }
  283. Set-Alias cvxml Convert-Xml -EA 0
  284.  
  285. function Remove-XmlNamespace {
  286. #.Synopsis
  287. #  Removes namespace definitions and prefixes from xml documents
  288. #.Description
  289. #  Runs an xml document through an XSL Transformation to remove namespaces from it if they exist.
  290. #  Entities are also naturally expanded
  291. #.Parameter Content
  292. #  Specifies a string that contains the XML to transform.
  293. #.Parameter Path
  294. #  Specifies the path and file names of the XML files to transform. Wildcards are permitted.
  295. #
  296. #  There will bne one output document for each matching input file.
  297. #.Parameter Xml
  298. #  Specifies one or more XML documents to transform
  299. [CmdletBinding(DefaultParameterSetName="Xml")]
  300. PARAM(
  301.    [Parameter(Position=1,ParameterSetName="Path",Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
  302.    [ValidateNotNullOrEmpty()]
  303.    [Alias("PSPath")]
  304.    [String[]]$Path
  305. ,
  306.    [Parameter(Position=1,ParameterSetName="Xml",Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
  307.    [ValidateNotNullOrEmpty()]
  308.    [Alias("Node")]
  309.    [System.Xml.XmlNode[]]$Xml
  310. ,
  311.    [Parameter(ParameterSetName="Content",Mandatory=$true,ValueFromPipeline=$true)]
  312.    [ValidateNotNullOrEmpty()]
  313.    [String[]]$Content
  314. ,
  315.    [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false)]
  316.    [ValidateNotNullOrEmpty()]
  317.    [Alias("StyleSheet")]
  318.    [String[]]$Xslt
  319. )
  320. BEGIN {
  321.    $StyleSheet = New-Object System.Xml.Xsl.XslCompiledTransform
  322.    $StyleSheet.Load(([System.Xml.XmlReader]::Create((New-Object System.IO.StringReader @"
  323. <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  324.   <xsl:output method="xml" indent="yes"/>
  325.   <xsl:template match="/|comment()|processing-instruction()">
  326.      <xsl:copy>
  327.         <xsl:apply-templates/>
  328.      </xsl:copy>
  329.   </xsl:template>
  330.  
  331.   <xsl:template match="*">
  332.      <xsl:element name="{local-name()}">
  333.         <xsl:apply-templates select="@*|node()"/>
  334.      </xsl:element>
  335.   </xsl:template>
  336.  
  337.   <xsl:template match="@*">
  338.      <xsl:attribute name="{local-name()}">
  339.         <xsl:value-of select="."/>
  340.      </xsl:attribute>
  341.   </xsl:template>
  342. </xsl:stylesheet>
  343. "@))))
  344.    [Text.StringBuilder]$XmlContent = [String]::Empty
  345. }
  346. PROCESS {
  347.    switch($PSCmdlet.ParameterSetName) {
  348.       "Content" {
  349.          $null = $XmlContent.AppendLine( $Content -Join "`n" )
  350.       }
  351.       "Path" {
  352.          foreach($file in Get-ChildItem $Path) {
  353.             [Xml]$Xml = Get-Content $file
  354.             Convert-Node -Xml $Xml $StyleSheet
  355.          }
  356.       }
  357.       "Xml" {
  358.          $Xml | Convert-Node $StyleSheet
  359.       }
  360.    }
  361. }
  362. END {
  363.    if($PSCmdlet.ParameterSetName -eq "Content") {
  364.       [Xml]$Xml = $XmlContent.ToString()
  365.       Convert-Node -Xml $Xml $StyleSheet
  366.    }
  367. }
  368. }
  369. Set-Alias rmns Remove-XmlNamespace -EA 0
  370.  
  371.  
  372.  
  373. function New-XDocument {
  374. #.Synopsis
  375. #   Creates a new XDocument (the new xml document type)
  376. #.Description
  377. #  This is the root for a new XML mini-dsl, akin to New-BootsWindow for XAML
  378. #  It creates a new XDocument, and takes scritpblock(s) to define it's contents
  379. #.Parameter root
  380. #   The root node name
  381. #.Parameter version
  382. #   Optional: the XML version. Defaults to 1.0
  383. #.Parameter encoding
  384. #   Optional: the Encoding. Defaults to UTF-8
  385. #.Parameter standalone
  386. #  Optional: whether to specify standalone in the xml declaration. Defaults to "yes"
  387. #.Parameter args
  388. #   this is where all the dsl magic happens. Please see the Examples. :)
  389. #
  390. #.Example
  391. # [string]$xml = New-XDocument rss -version "2.0" {
  392. #    channel {
  393. #       title {"Test RSS Feed"}
  394. #       link {"http`://HuddledMasses.org"}
  395. #       description {"An RSS Feed generated simply to demonstrate my XML DSL"}
  396. #       item {
  397. #          title {"The First Item"}
  398. #          link {"http`://huddledmasses.org/new-site-new-layout-lost-posts/"}
  399. #          guid -isPermaLink true {"http`://huddledmasses.org/new-site-new-layout-lost-posts/"}
  400. #          description {"Ema Lazarus' Poem"}
  401. #          pubDate {(Get-Date 10/31/2003 -f u) -replace " ","T"}
  402. #       }
  403. #    }
  404. # }
  405. #
  406. # C:\PS>$xml.Declaration.ToString()  ## I can't find a way to have this included in the $xml.ToString()
  407. # C:\PS>$xml.ToString()
  408. #
  409. # <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  410. # <rss version="2.0">
  411. #   <channel>
  412. #     <title>Test RSS Feed</title>
  413. #     <link>http ://HuddledMasses.org</link>
  414. #     <description>An RSS Feed generated simply to demonstrate my XML DSL</description>
  415. #     <item>
  416. #       <title>The First Item</title>
  417. #       <link>http ://huddledmasses.org/new-site-new-layout-lost-posts/</link>
  418. #       <guid isPermaLink="true">http ://huddledmasses.org/new-site-new-layout-lost-posts/</guid>
  419. #       <description>Ema Lazarus' Poem</description>
  420. #       <pubDate>2003-10-31T00:00:00Z</pubDate>
  421. #     </item>
  422. #   </channel>
  423. # </rss>
  424. #
  425. #
  426. # Description
  427. # -----------
  428. # This example shows the creation of a complete RSS feed with a single item in it.
  429. #
  430. # NOTE that the backtick in the http`: in the URLs in the input is unecessary, and I added the space after the http: in the URLs  in the output -- these are accomodations to PoshCode's spam filter. Backticks are not need in the input, and spaces do not appear in the actual output.
  431. #
  432. #
  433. #.Example
  434. # [XNamespace]$atom="http`://www.w3.org/2005/Atom"
  435. # C:\PS>[XNamespace]$dc = "http`://purl.org/dc/elements/1.1"
  436. #
  437. # C:\PS>New-XDocument ($atom + "feed") -Encoding "UTF-16" -$([XNamespace]::Xml +'lang') "en-US" -dc $dc {
  438. #    title {"Test First Entry"}
  439. #    link {"http`://HuddledMasses.org"}
  440. #    updated {(Get-Date -f u) -replace " ","T"}
  441. #    author {
  442. #       name {"Joel Bennett"}
  443. #       uri {"http`://HuddledMasses.org"}
  444. #    }
  445. #    id {"http`://huddledmasses.org/" }
  446. #
  447. #    entry {
  448. #       title {"Test First Entry"}
  449. #       link {"http`://HuddledMasses.org/new-site-new-layout-lost-posts/" }
  450. #       id {"http`://huddledmasses.org/new-site-new-layout-lost-posts/" }
  451. #       updated {(Get-Date 10/31/2003 -f u) -replace " ","T"}
  452. #       summary {"Ema Lazarus' Poem"}
  453. #       link -rel license -href "http`://creativecommons.org/licenses/by/3.0/" -title "CC By-Attribution"
  454. #       dc:rights { "Copyright 2009, Some rights reserved (licensed under the Creative Commons Attribution 3.0 Unported license)" }
  455. #       category -scheme "http`://huddledmasses.org/tag/" -term "huddled-masses"
  456. #    }
  457. # } | % { $_.Declaration.ToString(); $_.ToString() }
  458. #
  459. # <?xml version="1.0" encoding="UTF-16" standalone="yes"?>
  460. # <feed xml:lang="en-US" xmlns="http ://www.w3.org/2005/Atom">
  461. #   <title>Test First Entry</title>
  462. #   <link>http ://HuddledMasses.org</link>
  463. #   <updated>2009-07-29T17:25:49Z</updated>
  464. #   <author>
  465. #      <name>Joel Bennett</name>
  466. #      <uri>http ://HuddledMasses.org</uri>
  467. #   </author>
  468. #   <id>http ://huddledmasses.org/</id>
  469. #   <entry>
  470. #     <title>Test First Entry</title>
  471. #     <link>http ://HuddledMasses.org/new-site-new-layout-lost-posts/</link>
  472. #     <id>http ://huddledmasses.org/new-site-new-layout-lost-posts/</id>
  473. #     <updated>2003-10-31T00:00:00Z</updated>
  474. #     <summary>Ema Lazarus' Poem</summary>
  475. #     <link rel="license" href="http ://creativecommons.org/licenses/by/3.0/" title="CC By-Attribution" />
  476. #     <dc:rights>Copyright 2009, Some rights reserved (licensed under the Creative Commons Attribution 3.0 Unported license)</dc:rights>
  477. #     <category scheme="http ://huddledmasses.org/tag/" term="huddled-masses" />
  478. #   </entry>
  479. # </feed>
  480. #
  481. #
  482. # Description
  483. # -----------
  484. # This example shows the use of a default namespace, as well as additional specific namespaces for the "dc" namespace. It also demonstrates how you can get the <?xml?> declaration which does not appear in a simple .ToString().
  485. #
  486. # NOTE that the backtick in the http`: in the URLs in the input is unecessary, and I added the space after the http: in the URLs  in the output -- these are accomodations to PoshCode's spam filter. Backticks are not need in the input, and spaces do not appear in the actual output.#
  487. #
  488. Param(
  489.    [Parameter(Mandatory = $true, Position = 0)]
  490.    [System.Xml.Linq.XName]$root
  491. ,
  492.    [Parameter(Mandatory = $false)]
  493.    [string]$Version = "1.0"
  494. ,
  495.    [Parameter(Mandatory = $false)]
  496.    [string]$Encoding = "UTF-8"
  497. ,
  498.    [Parameter(Mandatory = $false)]
  499.    [string]$Standalone = "yes"
  500. ,
  501.    [Parameter(Position=99, Mandatory = $false, ValueFromRemainingArguments=$true)]
  502.    [PSObject[]]$args
  503. )
  504. BEGIN {
  505.    $script:NameSpaceHash = New-Object 'Dictionary[String,XNamespace]'
  506.    if($root.NamespaceName) {
  507.       $script:NameSpaceHash.Add("", $root.Namespace)
  508.    }
  509. }
  510. PROCESS {
  511.    New-Object XDocument (New-Object XDeclaration $Version, $Encoding, $standalone),(
  512.       New-Object XElement $(
  513.          $root
  514.          while($args) {
  515.             $attrib, $value, $args = $args
  516.             if($attrib -is [ScriptBlock]) {
  517.                # Write-Verbose "Preparsed DSL: $attrib"
  518.                $attrib = ConvertFrom-XmlDsl $attrib
  519.                Write-Verbose "Reparsed DSL: $attrib"
  520.                &$attrib
  521.             } elseif ( $value -is [ScriptBlock] -and "-Content".StartsWith($attrib)) {
  522.                $value = ConvertFrom-XmlDsl $value
  523.                &$value
  524.             } elseif ( $value -is [XNamespace]) {
  525.                New-Object XAttribute ([XNamespace]::Xmlns + $attrib.TrimStart("-")), $value
  526.                $script:NameSpaceHash.Add($attrib.TrimStart("-"), $value)
  527.             } else {
  528.                New-Object XAttribute $attrib.TrimStart("-"), $value
  529.             }
  530.          }
  531.       ))
  532. }
  533. }
  534.  
  535. Set-Alias xml New-XDocument -EA 0
  536. Set-Alias New-Xml New-XDocument -EA 0
  537.  
  538. function New-XAttribute {
  539. #.Synopsys
  540. #   Creates a new XAttribute (an xml attribute on an XElement for XDocument)
  541. #.Description
  542. #  This is the work-horse for the XML mini-dsl
  543. #.Parameter name
  544. #   The attribute name
  545. #.Parameter value
  546. #  The attribute value
  547. Param([Parameter(Mandatory=$true)]$name,[Parameter(Mandatory=$true)]$value)
  548.    New-Object XAttribute $name, $value
  549. }
  550. Set-Alias xa New-XAttribute -EA 0
  551. Set-Alias New-XmlAttribute New-XAttribute -EA 0
  552.  
  553.  
  554. function New-XElement {
  555. #.Synopsys
  556. #   Creates a new XElement (an xml tag for XDocument)
  557. #.Description
  558. #  This is the work-horse for the XML mini-dsl
  559. #.Parameter tag
  560. #   The name of the xml tag
  561. #.Parameter args
  562. #   this is where all the dsl magic happens. Please see the Examples. :)
  563. Param(
  564.    [Parameter(Mandatory = $true, Position = 0)]
  565.    [System.Xml.Linq.XName]$tag
  566. ,
  567.    [Parameter(Position=99, Mandatory = $false, ValueFromRemainingArguments=$true)]
  568.    [PSObject[]]$args
  569. )
  570. #  BEGIN {
  571.    #  if([string]::IsNullOrEmpty( $tag.NamespaceName )) {
  572.       #  $tag = $($script:NameSpaceStack.Peek()) + $tag
  573.       #  if( $script:NameSpaceStack.Count -gt 0 ) {
  574.          #  $script:NameSpaceStack.Push( $script:NameSpaceStack.Peek() )
  575.       #  } else {
  576.          #  $script:NameSpaceStack.Push( $null )
  577.       #  }      
  578.    #  } else {
  579.       #  $script:NameSpaceStack.Push( $tag.Namespace )
  580.    #  }
  581. #  }
  582. PROCESS {
  583.   New-Object XElement $(
  584.      $tag
  585.      while($args) {
  586.         $attrib, $value, $args = $args
  587.         if($attrib -is [ScriptBlock]) { # then it's content
  588.            &$attrib
  589.         } elseif ( $value -is [ScriptBlock] -and "-Content".StartsWith($attrib)) { # then it's content
  590.            &$value
  591.         } elseif ( $value -is [XNamespace]) {
  592.            New-Object XAttribute ([XNamespace]::Xmlns + $attrib.TrimStart("-")), $value
  593.            $script:NameSpaceHash.Add($attrib.TrimStart("-"), $value)
  594.         } else {
  595.            New-Object XAttribute $attrib.TrimStart("-"), $value
  596.         }        
  597.      }
  598.    )
  599. }
  600. #  END {
  601.    #  $null = $script:NameSpaceStack.Pop()
  602. #  }
  603. }
  604. Set-Alias xe New-XElement
  605. Set-Alias New-XmlElement New-XElement
  606.  
  607. function ConvertFrom-XmlDsl {
  608. Param([ScriptBlock]$script)
  609.    $parserrors = $null
  610.    $global:tokens = [PSParser]::Tokenize( $script, [ref]$parserrors )
  611.    $duds = $global:tokens | Where-Object { $_.Type -eq "Command" -and !$_.Content.Contains('-') -and ($(Get-Command $_.Content -Type Cmdlet,Function,ExternalScript -EA 0) -eq $Null) }
  612.    [Array]::Reverse( $duds )
  613.    
  614.    [string[]]$ScriptText = "$script" -split "`n"
  615.  
  616.    ForEach($token in $duds ) {
  617.       # replace : notation with namespace notation
  618.       if( $token.Content.Contains(":") ) {
  619.          $key, $localname = $token.Content -split ":"
  620.          $ScriptText[($token.StartLine - 1)] = $ScriptText[($token.StartLine - 1)].Remove( $token.StartColumn -1, $token.Length ).Insert( $token.StartColumn -1, "'" + $($script:NameSpaceHash[$key] + $localname) + "'" )
  621.       } else {
  622.          $ScriptText[($token.StartLine - 1)] = $ScriptText[($token.StartLine - 1)].Remove( $token.StartColumn -1, $token.Length ).Insert( $token.StartColumn -1, "'" + $($script:NameSpaceHash[''] + $token.Content) + "'" )
  623.       }
  624.       # insert 'xe' before everything (unless it's a valid command)
  625.       $ScriptText[($token.StartLine - 1)] = $ScriptText[($token.StartLine - 1)].Insert( $token.StartColumn -1, "xe " )
  626.    }
  627.    Write-Output ([ScriptBlock]::Create( ($ScriptText -join "`n") ))
  628. }
  629.    
  630. Export-ModuleMember -alias * -function New-XDocument, New-XAttribute, New-XElement, Remove-XmlNamespace, Convert-Xml, Select-Xml, Format-Xml

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me