Home > Mobile >  Adding an xml fragment to an existing document
Adding an xml fragment to an existing document

Time:07-23

What is the least effort way to insert an xml fragment into an existing [xml] value ([xml] of course is syntactic sugar for System.Xml.XmlDocument)?

For example, say we have this newrelic.config file:

<configuration xmlns="urn:newrelic-config" agentEnabled="true">
  <service licenseKey="**redacted**" />
  <application>
    <name>My Application</name>
  </application>
  <log level="info" />
  <transactionTracer enabled="true" transactionThreshold="apdex_f" stackTraceThreshold="500" recordSql="obfuscated" explainEnabled="false" explainThreshold="500" />
  <crossApplicationTracer enabled="true" />
  <errorCollector enabled="true">
    <ignoreErrors>
      <exception>System.IO.FileNotFoundException</exception>
      <exception>System.Threading.ThreadAbortException</exception>
    </ignoreErrors>
    <ignoreStatusCodes>
      <code>401</code>
      <code>404</code>
    </ignoreStatusCodes>
  </errorCollector>
  <browserMonitoring autoInstrument="true" />
  <threadProfiling>
    <ignoreMethod>System.Threading.WaitHandle:InternalWaitOne</ignoreMethod>
    <ignoreMethod>System.Threading.WaitHandle:WaitAny</ignoreMethod>
  </threadProfiling>
</configuration>

And we want to insert an instrumentation node under configuration; not just one element but a whole fragment:

  <instrumentation>
    <applications>
      <application name="MyApplication.exe" />
    </applications>
  </instrumentation>

Most other answers on SO flail around a lot with creating XML elements and attributes; for example this one:

 # Create ExitProcess node
  $exitProcessNode = $Document.CreateElement('ExitProcess')

  # Create ProcessName node, add process name as inner text
  $processNameNode = $Document.CreateElement('ProcessName')
  $processNameNode.InnerText = $ProcessName

  # Create ForceClose node, add setting as inner text 
  $forceCloseNode = $Document.CreateElement('ProcessName')
  $ForceCloseNode.InnerText = $ForceClose.IsPresent

How can I make this shorter?

Note: I don't have huge documents so performance is not really an issue; I'm more after shorter PowerShell code.

CodePudding user response:

The correct way to do this is to import the node (ImportNode) in the main document and than append the concerned child (AppendChild) to the specific node:

$Main = [xml]'
<configuration xmlns="urn:newrelic-config" agentEnabled="true">
  <service licenseKey="**redacted**" />
  <application>
    <name>My Application</name>
  </application>
  <log level="info" />
  <transactionTracer enabled="true" transactionThreshold="apdex_f" stackTraceThreshold="500" recordSql="obfuscated" explainEnabled="false" explainThreshold="500" />
  <crossApplicationTracer enabled="true" />
  <errorCollector enabled="true">
    <ignoreErrors>
      <exception>System.IO.FileNotFoundException</exception>
      <exception>System.Threading.ThreadAbortException</exception>
    </ignoreErrors>
    <ignoreStatusCodes>
      <code>401</code>
      <code>404</code>
    </ignoreStatusCodes>
  </errorCollector>
  <browserMonitoring autoInstrument="true" />
  <threadProfiling>
    <ignoreMethod>System.Threading.WaitHandle:InternalWaitOne</ignoreMethod>
    <ignoreMethod>System.Threading.WaitHandle:WaitAny</ignoreMethod>
  </threadProfiling>
</configuration>'

$Fragment = [xml]'
  <instrumentation>
    <applications>
      <application name="MyApplication.exe" />
    </applications>
  </instrumentation>'
$NewNode = $Main.ImportNode($Fragment.instrumentation, $True)
$Main.Configuration.AppendChild($NewNode)
[System.Xml.Linq.XDocument]::Parse($Main.OuterXml).ToString()

<configuration xmlns="urn:newrelic-config" agentEnabled="true">
  <service licenseKey="**redacted**" />
  <application>
    <name>My Application</name>
  </application>
  <log level="info" />
  <transactionTracer enabled="true" transactionThreshold="apdex_f" stackTraceThreshold="500" recordSql="obfuscated" explainEnabled="false" explainThreshold="500" />
  <crossApplicationTracer enabled="true" />
  <errorCollector enabled="true">
    <ignoreErrors>
      <exception>System.IO.FileNotFoundException</exception>
      <exception>System.Threading.ThreadAbortException</exception>
    </ignoreErrors>
    <ignoreStatusCodes>
      <code>401</code>
      <code>404</code>
    </ignoreStatusCodes>
  </errorCollector>
  <browserMonitoring autoInstrument="true" />
  <threadProfiling>
    <ignoreMethod>System.Threading.WaitHandle:InternalWaitOne</ignoreMethod>
    <ignoreMethod>System.Threading.WaitHandle:WaitAny</ignoreMethod>
  </threadProfiling>
  <instrumentation xmlns="">
    <applications>
      <application name="MyApplication.exe" />
    </applications>
  </instrumentation>
</configuration>

CodePudding user response:

This function appends a string xml fragment as a child of an xmlelement:

function AppendChildXml {
     param (
         [System.Xml.XmlElement]$element,
         [string]$fragment
     )
     [System.Xml.XmlDocumentFragment]$fragxml = $element.OwnerDocument.CreateDocumentFragment();
     $fragxml.innerXml = $fragment;
     $element.AppendChild($fragxml);
}

Since the function has an XmlElement as its first parameter, this makes it easy to mix with the "PowerShell way" of referring to deep elements with properties, e.g. $xmldoc.configuration.errorCollector.ignoreErrors

For example:

# read in the existing newrelic.config
[xml]$newRelicConfigXml = [xml]::new();
$newRelicConfigXml.Load((Convert-Path $newRelicConfigPath));

# instrumentation section to be added
[string]$fragment = '<instrumentation><applications><application name="MyApplication.exe"/></applications></instrumentation>'

# add instrumentation under configuration
AppendChildXml $newRelicConfigXml.configuration $fragment
  • Related