0

I am trying make this XML:

<?xml version="1.0" encoding="UTF-8"?>
<nfeProc>
  <NFe>
    <infNFe>
      <det nItem="1">
        <prod>
          <vProd>180.00</vProd>
        </prod>
        <imposto>
          <ICMS>
            <ICMS00>
              <vBC>180.00</vBC>
            </ICMS00>
          </ICMS>
          <PIS>
            <PISNT>04</PISNT>
          </PIS>
    </infNFe>
  </NFe>
</nfeProc>

Looks like:

<?xml version="1.0" encoding="UTF-8"?>
<nfeProc>
  <NFe>
    <infNFe>
      <det nItem="1">
        <prod>
          <vProd>180.00</vProd>
        </prod>
        <imposto>
          <ICMS>
            <ICMS00>
              <vBC>180.00</vBC>
            </ICMS00>
          </ICMS>
          <PIS>
            <PISAliq>0.65</PISAliq>
          </PIS>
    </infNFe>
  </NFe>
</nfeProc>

Couldn't find a easy way to remove the <PISNT>04</PISNT> so I went with this:

   ($produto.imposto.PIS.ChildNodes | Where-Object {$_.Name -eq "PISNT"}) | ForEach-Object {
      [void]$_.ParentNode.RemoveChild($_)
   }

I don't like, but works. My problem is to add the node <PISAliq>0.65</PISAliq>, already tried everything that I found and still can't work.

My actual code is:

Get-ChildItem $empresaPath -Filter "*.xml" | ForEach-Object {
   [xml]$xml = Get-Content $_.FullName
   $produtos = $xml.nfeProc.NFe.infNFe.det
   foreach ($produto in $produtos) {
      $cfop = $produto.prod.CFOP
      if ($cfop -eq "5102") {
         $prodVprod = $produto.prod.vProd
         $produto.imposto.ICMS.ICMS00.vBC = $prodVprod
         if ($produto.imposto.PIS.PISNT) {
            ($produto.imposto.PIS.ChildNodes | Where-Object {$_.Name -eq "PISNT"}) | ForEach-Object {
            [void]$_.ParentNode.RemoveChild($_)
         }
         $xmlElt = $produto.imposto.PIS.CreateElement("PISAliq")
         $xmlText = $produto.imposto.PIS.CreateTextNode("0.65")
         $xmlElt.AppendChild($xmlText)
         $xmlElt = $xml.CreateElement("PISAliq")
         $xmlText = $xml.CreateTextNode("0.65")
         $xmlElt.AppendChild($xmlText)
      }
         $xml.Save($_.FullName)
      }
      else {
         Write-Host "$($_.BaseName) CFOP $($cfop) inválido" -ForegroundColor Red
      }
   }
}

I get this error using this code:

+ CategoryInfo          : InvalidOperation: (CreateElement:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound

Why is so hard add one tag on PowerShell? I appreciate any help, thanks in advance for your time.

1
  • The error stems from trying to call .CreateElement() and .CreateTextNode() on an element node ($produto.imposto.PIS); these methods only exist at the document level ($xml), as also present in your code. The other problem is that you're not adding the text child node to the newly created element - Sage's answer shows how to fix that (just assign to .InnerText). Commented Jan 18, 2020 at 13:29

2 Answers 2

2

You need to create the node using $xml.CreateElement then adding the value and finally append it to the desired node.

Example — Based on your sample

#$PISNode = Select-Xml -Xml $xml -XPath '//det/PIS'
$PISNode = $xml.nfeProc.NFe.infNFe.det.PIS
$NewPISALIQNode = $xml.CreateElement("PISAliq")
$NewPISALIQNode.InnerText = 0.65
$PISNode.AppendChild($NewPISALIQNode)

If you need to select particular nodes based on some specific conditions, I recommend using Select-XML along with Xpath to select the specific nodes your need.

XPath can be a bit tricky since you need to learn its synta but ample resources are available online.

Example #2 — Getting a specific group of PIS nodes

This example get PIS nodes that have their ICMS/ICMS500/vBC value to 180.00

$PISNode = Select-Xml -Xml $xml -XPath "//det[ICMS/ICMS00/vBC[.='180.00']]"
if ($PISNode.count -ge 1){$PISNode = $PISNode.Node.PIS}

Additional note

The xml sample in the question was incorrect at the time of me writing this. Therefore, I removed the <imposto> tag and closed the </det> tag in order to obtain a valid XML.

Here is the actual XML these examples were tested against.

<?xml version="1.0" encoding="UTF-8"?>
<nfeProc>
  <NFe>
    <infNFe>
      <det nItem="1">
        <prod>
          <vProd>180.00</vProd>
        </prod>
          <ICMS>
            <ICMS00>
              <vBC>180.00</vBC>
            </ICMS00>
          </ICMS>
          <PIS>
            <PISNT>04</PISNT>
          </PIS>
          </det>
    </infNFe>
  </NFe>
</nfeProc>

References

MDN - XPath

W3schools - XML and XPath

devhints - Xpath cheatsheet

Sign up to request clarification or add additional context in comments.

7 Comments

Could be various <det nItem="1"> how I could choose the correct one this way?
You could use Xpath syntax with Select-XML to determine which nodes are selected in the first place. I added a second example in my answer to demonstrate how to query a specific (or group of) node based on an arbitrary criteria.
I tried $PISNode = Select-Xml -Xml $xml -XPath '//det/PIS' and $PISNode = $xml.nfeProc.NFe.infNFe.det.PIS. Both returns null.
Tried the xpath on freeformatter.com/xpath-tester.html and was not working too. I found that this line on xml <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> was making the test not work. Without this the test works, but the script still can't find any xpath inside the file.
@RaphaelSouza See the "addtional note" I added in my answer. If you test against that xml, it will work on the freeformatter site. The original xml you provided was invalid because of the reasons mentionned in my edit.
|
1

You can also use XDocument.
XDocument is for C#, but you can use it well in Powershell if you add some methods.

using namespace System.Xml
using namespace System.Xml.Linq
using assembly System.Xml.Linq

function Add-ExMethod ($NamespaceTable) {

    Begin {
        $nsmgr = [XmlNamespaceManager]::new([NameTable]::new());
        $NamespaceTable.GetEnumerator() | foreach { $nsmgr.AddNamespace($_.Name, $_.Value) }
    }
    Process {
        $_ |
        Add-Member ScriptMethod elem  { [XPath.Extensions]::XPathSelectElement($this, $args, $nsmgr)  }.GetNewClosure() -PassThru |
        Add-Member ScriptMethod elems { [XPath.Extensions]::XPathSelectElements($this, $args, $nsmgr) }.GetNewClosure() -PassThru
    }
}


$ns = @{
    n = [XNamespace]"http://www.portalfiscal.inf.br/nfe"
    s = [XNamespace]"http://www.w3.org/2000/09/xmldsig#"
}

Get-ChildItem $empresaPath *.xml | ForEach-Object {

    $doc = [XDocument]::Load($_.FullName) | Add-ExMethod $ns
    $produtos = $doc.elems("//n:det") | Add-ExMethod $ns

    foreach ($p in $produtos) {

       # check CFOP value
        $cfop = $p.elem(".//n:CFOP")
        if ($cfop.Value -ne "5102") { Write-Warning "$_ CFOP $($cfop.Value) inválido"; continue }

        # update vBC value
        $p.elem(".//n:vBC").Value = $p.elem(".//n:vProd").Value

        # replace PISNTwith a new element
        try { $p.elem(".//n:PISNT").ReplaceWith([XElement]::new($ns.n + "PISAliq", "0.65")) } catch {}
    }

    $doc.Save($_.FullName)
}

Without xpath:

Get-ChildItem $empresaPath -Filter *.xml | ForEach-Object {

    $doc = [xml](Get-Content $_.FullName)

    foreach ($p in $doc.nfeProc.NFe.infNFe.det) {

        # check CFOP value
        if ($p.prod.CFOP -ne "5102") { Write-Warning "$_ CFOP $($p.prod.CFOP) invalid"; continue }

        # update vBC value
        $p.imposto.ICMS.ICMS00.vBC = $p.prod.vProd

        # create a new element
        $newElem = $doc.CreateElement("PISAliq", "http://www.portalfiscal.inf.br/nfe")
        $newElem.InnerText = "0.65"

        # replace PISNT with a new element
        $pis = $p.imposto.PIS
        [void]$pis.ReplaceChild($newElem, $pis["PISNT"])
    }

    $doc.Save($_.FullName)
}

6 Comments

I wanted a PowerShell solution, but this way is so easier that I gave a try. The problem is that is not working and I have no idea how to fix it, since I never used linq. $doc is loading the XML, but $produtos = $doc.elems("//det") | Add-ExMethod is empty. I will try to change a bit to see if I find a way to make this work. But looks like there's a path problem.
Probably because 'cast notation' was used for the variable $xml. Like this, [xml]xml = .... Therefore, XDocument has been converted to XmlDocument. Before run the script on ISE, execute the following command to remove the variable $xml. Remove-Variable xml
I removed all my variables and stil $produtos came empty. Don't seens that the xpath is working. The $doc FirstNode, LastNode, root it's entire XML. The DocumentType is empty and the NodeType is document.
I did a search, found 3 xmlns. <nfeProc versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe"> <NFe xmlns="http://www.portalfiscal.inf.br/nfe"> <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> This change the xpath?
Yes, you need to specify the xpath in consideration of the namespace. At that time, XmlNamespaceManager is required.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.