PoshCode Logo PowerShell Code Repository

Xml Module 4.1 (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/1678"></script>download | new post

Never content to leave well-enough alone, I’ve rewritten my XML DSL with 100% less cruft. 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.

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, however, important to note that this means that the returned results will not have namespaces in them, even if the input XML did, and only raw XmlNodes are returned from this function, so the output isn’t 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

4.1: Minor tweaks while writing a blog post

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