PoshCode Logo PowerShell Code Repository

Xml Module 4.2 by Joel Bennett 23 months ago (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/1681"></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

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