none
Copy nodes, based on nested nodes, between xml files. RRS feed

  • Question

  • Hi,

    I have 2 XML files. I need to copy some nodes from one to the other. This will be determined by a node nested within those nodes.

    This will be run from a batch script, which needs to pass over the variable to search for. I originally tried to accomplish this in the batch script itself, but it got very messy, very quickly and I don't even know if it is possible.

    Here is an example:

    So the batch script has the variable %cata% which is Category A and it needs to pass that to the Powershell script first.

    Then the Powershell script needs to search for all <Category> nodes tagged Category A. It then needs to copy all <Item> nodes that include these.

    XML 1:

    <?xml version="1.0" standalone="yes"?>
      <Program>
        <Item>
          <ID>01</ID>
          <Title>Item 1</Title>
          <var1 />
          <var2 />
          <Category>Category A</Category>
          <var3 />
          <var4 />
        </Item>
        <Item>
          <ID>02</ID>
          <Title>Item 2</Title>
          <var1 />
          <var2 />
          <Category>Category A</Category>
          <var3 />
          <var4 />
        </Item>
        <Item>
          <ID>03</ID>
          <Title>Item 3</Title>
          <var1 />
          <var2 />
          <Category>Category B</Category>
          <var3 />
          <var4 />
        </Item>
        <Settings>
           Various Settings
        </Settings>
      </Program>

    XML 2:

    <?xml version="1.0" standalone="yes"?>
      <Program>
        <Item>
          <ID>04</ID>
          <Title>Item 4</Title>
          <var1 />
          <var2 />
          <Category>Category A</Category>
          <var3 />
          <var4 />
        </Item>
        <Item>
          <ID>05</ID>
          <Title>Item 5</Title>
          <var1 />
          <var2 />
          <Category>Category A</Category>
          <var3 />
          <var4 />
        </Item>
        <Settings>
           Various Settings
        </Settings>
      </Program>

    After the script has run, XML 2 should look something like this:


    <?xml version="1.0" standalone="yes"?>
      <Program>
        <Item>
          <ID>01</ID>
          <Title>Item 1</Title>
          <var1 />
          <var2 />
          <Category>Category A</Category>
          <var3 />
          <var4 />
        </Item>
        <Item>
          <ID>02</ID>
          <Title>Item 2</Title>
          <var1 />
          <var2 />
          <Category>Category A</Category>
          <var3 />
          <var4 />
        </Item>
        <Item>
        <Item>
          <ID>04</ID>
          <Title>Item 4</Title>
          <var1 />
          <var2 />
          <Category>Category A</Category>
          <var3 />
          <var4 />
        </Item>
        <Item>
          <ID>05</ID>
          <Title>Item 5</Title>
          <var1 />
          <var2 />
          <Category>Category A</Category>
          <var3 />
          <var4 />
        </Item>
        <Settings>
           Various Settings
        </Settings>
      </Program>


    Now I would be very happy just to get that part done, but there is more.

    Each of these items has a corresponding folder named <Title>.39DJOU008(string of varying numbers/letters)JJ8SBD9 (residing in a different directory)

    Each node that is copied from the xml file, also needs the corresponding folder copying to a new destination.

    This is what I have come up with so far, am I on the right tracks?

    $oldxml = Get-ChildItem c:\xml\oldxml.xml
    $newxml = Get-ChildItem c:\xml2\newxml.xml
    {
    (Get-Content $newxml) -copy '<Item>.*<Category>Category A</Category>.*</Item>' | Out-File $newxml -encoding utf8
    }

    The copying of the folders is not essential, I can probably accomplish that in the batch file, especially if Powershell can output a txt document listing the <Title>'s that it copied.

    Thanks



    Wednesday, July 30, 2014 10:08 PM

Answers

  • Using the sample XML files you posted, this seems to work:

    $oldXml = [xml](Get-Content .\old.xml)
    $newXml = [xml](Get-Content .\new.xml)
    
    $oldProgramNode = $oldXml.SelectSingleNode('//Program')
    $newProgramNode = $newXml.SelectSingleNode('//Program')
    
    foreach ($oldCategoryAItemNode in $oldProgramNode.SelectNodes('Item[Category = "Category A"]'))
    {
        $id = $oldCategoryAItemNode.SelectSingleNode('ID/text()').Value
        
        if ($id)
        {
            $matchingNewNode = $newProgramNode.SelectSingleNode("Item[ID='$id']")
            if ($matchingNewNode)
            {
                $null = $newProgramNode.RemoveChild($matchingNewNode)
            }
        }
    
        $newItemNode = $newXml.ImportNode($oldCategoryAItemNode, $true)
        $null = $newProgramNode.AppendChild($newItemNode)
    }
    
    $newXml.Save("$pwd\combined.xml")
    

    • Marked as answer by jack_90824590 Thursday, July 31, 2014 2:41 AM
    Thursday, July 31, 2014 2:12 AM

All replies

  • It doesn't make any sense.  Which do you want to do?  Copy nodes or copy files?


    ¯\_(ツ)_/¯

    Wednesday, July 30, 2014 11:07 PM
  • Copy Nodes.

    Copy the <Item> nodes that are "Category 1" (<Category> Node)


    I misused -copy

    • Edited by jack_90824590 Wednesday, July 30, 2014 11:30 PM added
    Wednesday, July 30, 2014 11:13 PM
  • If you look at XML 2 AFTER the script has run (3rd XML shown) , some contents have copied over from XML 1 (From 1st XML shown into 2nd XML Shown to create the 3rd XML shown)
    • Edited by jack_90824590 Wednesday, July 30, 2014 11:18 PM Added content
    Wednesday, July 30, 2014 11:16 PM
  • I am now working with:

    $oldxml = New-Object XML
    $oldxml.Load("C:\xml\old.xml")
    
    $newxml = New-Object XML
    $newxml.Load("C:\xml\new.xml")
    
    $oldxml.DocumentElement.InsertAfter($oldxml.ImportNode($newxml.SelectSingleNode($node), $true), $afternode)

    But I am not getting any further.

    I need to copy the Item node if it contains the Category node I am looking for.

    Wednesday, July 30, 2014 11:24 PM
  • What are you trying to copy where.  You just say copy. The word "copy" implies from/to.  Copy what from where to what where?


    ¯\_(ツ)_/¯

    Wednesday, July 30, 2014 11:48 PM
  • $xml.SelectNodes("//text()[contains(., 'Category A')]")


    ¯\_(ツ)_/¯


    • Edited by jrv Wednesday, July 30, 2014 11:59 PM
    Wednesday, July 30, 2014 11:51 PM
  • I need to copy everything between and including the <Item> </Item> tags, only IF Category A is in the <category> tags.
    Wednesday, July 30, 2014 11:57 PM
  • copy from old.xml to new.xml

    new.xml just needs updating with all items that are category 1 in old.xml

    old.xml must remain intact.

    Thursday, July 31, 2014 12:00 AM
  • Here are all of the parent nodes that contain the text.

    $nodes=$f1.SelectNodes("//Category/text()[contains(., 'Category A')]").ParentNode.ParentNode


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 12:04 AM
  • Thursday, July 31, 2014 12:10 AM
  • You have a problem.  If you name an element "Item" it causes a conflict in XML unless you wrap it in a namespace.

    This can be done but it takes a lot of manipulation.  You will need to be pretty handy with XML and with PowerShell.  It is easier to just change "Item" to something else.


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 12:13 AM
  • The query I posted does exactly that.


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 12:14 AM
  • Ok lets name it Thing.

    I tried this:

    $oldxml = New-Object XML
    $oldxml.Load("C:\xml\old.xml")
    
    $newxml = New-Object XML
    $newxml.Load("C:\xml\new.xml")
    
    $oldxml.DocumentElement.InsertAfter($oldxml.ImportNode($newxml.SelectSingleNode($f1.SelectNodes("//Category/text()[contains(., 'Category A')]").ParentNode.ParentNode), $true), 'Program')

    and got this error:

    You cannot call a method on a null-valued expression.
    At C:\xml\test.ps1:7 char:1
    + $oldxml.DocumentElement.InsertAfter($oldxml.ImportNode($newxml.SelectSingleNode( ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
        + FullyQualifiedErrorId : InvokeMethodOnNull

    Thursday, July 31, 2014 12:22 AM
  • That is what I mean about being good with XML.  You cannot copy nodes from pne document to another.  You must create a copy and add that.  There is no "copy" method in XML.


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 12:34 AM
  • So how do i create a new XML and add the data from both?


    Wouldn't that make it impossible?
    Thursday, July 31, 2014 12:38 AM
  • So how do i create a new XML and add the data from both?

    Recipe:

    1. On target xml object get a document fragment object.
    2.  Assign source node 'OuterXML' to fragment
    3.  Append fragment to target node collection.

    $frag=$target.CreateDocumentFragment()
    $frag.outerXML=$source.OuterXml
    $target.Program.AppendChild($frag)

    It is "Child" play.

    \


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 12:43 AM
  • Or do you mean you can move but not copy?

    So create a temp copy of old.xml, then MOVE the required data over, then delete the temp copy?

    Thursday, July 31, 2014 12:45 AM
  • Or do you mean you can move but not copy?

    So create a temp copy of old.xml, then MOVE the required data over, then delete the temp copy?

    "Move" implies "copy". You cannot copy using any technique. Documents are isolated. YOu can copy the XML text but not the nodes.  As above you can use the XML text to create identical nodes.

    You will have issues if the documents are bound to a schema or a namespace.  These things must be resolved and respected.


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 12:49 AM
  • HaHa :)

    OK, but I am not sure what to do with that.  How do I work that in?

    Thursday, July 31, 2014 12:54 AM
  • Or do you mean you can move but not copy?

    So create a temp copy of old.xml, then MOVE the required data over, then delete the temp copy?

    "Move" implies "copy". You cannot copy using any technique. Documents are isolated. YOu can copy the XML text but not the nodes.  As above you can use the XML text to create identical nodes.

    You will have issues if the documents are bound to a schema or a namespace.  These things must be resolved and respected.


    ¯\_(ツ)_/¯

    Ok, can I rename the second xml as a txt file, write the contents from the 1st xml as text at the 3rd line. then rename back to xml.  If you know what I mean?
    Thursday, July 31, 2014 12:59 AM
  • This is not entirely true. XmlDocument has an ImportNode method which allows you (using the DOM, not text) to copy nodes or node trees from one document to another.

    That's fairly straightforward, however, I have a question:  are your Item "ID" nodes key values?  When the script finds a "Category A" Item in old.xml, does it need to see if a matching Item ID already exists in new xml, and if so, just update that item?  Or is it okay to just copy anything with Category A, regardless of what's already in the new xml?

    • Proposed as answer by jrv Thursday, July 31, 2014 1:26 AM
    • Unproposed as answer by jrv Thursday, July 31, 2014 1:26 AM
    Thursday, July 31, 2014 1:09 AM
  • That is a very good point and one that I forgot to mention.

    Yes, It would be great if it could recognize if the item already exists and skip it.

    But the data could be removed from new.xml first, that is easier right? old.xml will always hold the new data first, as well as the old data.  This would make having to match ID numbers irrelevant.

    Just delete all the <Items> that are Category A from new.xml

    Then copy everything over again, with the additional new items.

    On another note, i imagine the directories are going to be a problem.

    Thursday, July 31, 2014 1:26 AM
  • Good point. Yes Import can be used but we do have an issue with ID which is a reserved attribute.   Again - using a namespace can alter this.

    The issue is that without fixing all of these things you cannot even do a text copy and paste and end up with legal XML.

    Perhaps you should tell us what it is you are really trying to do.  The posted XML does not look very useful for anything. Even if we "Import" you wil have  to update the ID but that may be an identifier inn the source system and changing it may break the data.


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 1:29 AM
  • That is a very good point and one that I forgot to mention.

    Yes, It would be great if it could recognize if the item already exists and skip it.

    But the data could be removed from new.xml first, that is easier right? old.xml will always hold the new data first, as well as the old data.  This would make having to match ID numbers irrelevant.

    Just delete all the <Items> that are Category A from new.xml

    Then copy everything over again, with the additional new items.

    On another nYou are ote, i imagine the directories are going to be a problem.

    You are still thinking in terms of text.  This is XML.

    What you are asking is all possible but it is not something that can be done in 5 lines or less.  I am only trying to point you at how to learn to build a solution.  I am not trying to write the whole solution.  From your questions and confusion I am pretty sure this will go on for some time because you keep discovering new things that you need.

    We can answer questions and point you at resources but you have to learn the technology and write the script.  It is a steep learning curve but a good way to learn XML programming.


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 1:52 AM
  • Yes the ID is important and I just realized something else.  The directories are named <Title>.<ID>

    Sure, the XML's are for a frontend program for retro gaming emulators.  The app lacks the ability to split the platforms up at present, but it is portable.  So I created multiple instances of the the program (one for each platform) and one "main" instance that would contain all the platforms.  My idea being when the app gets the feature further down the line, the main instance would be ready and I could just delete the other platform instances when they were no longer needed.

    Now I set all mine up manually, but others would like to do the same thing, but don't have the time to set everything up.  So I set out to create a few scripts which would do everything automatically for everyone else.  I got most of it done using batch scripts, which I can handle.  But importing the platform data and image folders over proved very difficult using the command-line and I ended up here because Powershell is capable.  However, I am not well versed in it.

    So I need the Powershell script to pull the data for a specific platform and inject it into the new instance xml (which already has some data in it that can't be pulled from the main instance xml) and copy over the corresponding directories that contain the artwork.

    I hope that makes sense, I was pretty brief, but it would be a long winded explanation, hence why I was trying to avoid it.

    Thursday, July 31, 2014 1:55 AM
  • That is a very good point and one that I forgot to mention.

    Yes, It would be great if it could recognize if the item already exists and skip it.

    But the data could be removed from new.xml first, that is easier right? old.xml will always hold the new data first, as well as the old data.  This would make having to match ID numbers irrelevant.

    Just delete all the <Items> that are Category A from new.xml

    Then copy everything over again, with the additional new items.

    On another nYou are ote, i imagine the directories are going to be a problem.

    You are still thinking in terms of text.  This is XML.

    What you are asking is all possible but it is not something that can be done in 5 lines or less.  I am only trying to point you at how to learn to build a solution.  I am not trying to write the whole solution.  From your questions and confusion I am pretty sure this will go on for some time because you keep discovering new things that you need.

    We can answer questions and point you at resources but you have to learn the technology and write the script.  It is a steep learning curve but a good way to learn XML programming.


    ¯\_(ツ)_/¯

    Yes sorry, I wasn't actually planning on learning Powershell, I have used it a couple of times before, but I wouldn't use it often enough to invest the time in learning it.  I don't program, I design.

    Batch/Command-Line, HTML, CSS, etc. is all I can really handle.

    I sort of fell here trying to help out a dev who is a really nice guy and very very busy.  I had no idea it would be so complicated, I honestly thought it would be pretty straight forward.

    I am in over my head and I now fear I have wasted your time as well as mine.

    Thursday, July 31, 2014 2:02 AM
  • Using the sample XML files you posted, this seems to work:

    $oldXml = [xml](Get-Content .\old.xml)
    $newXml = [xml](Get-Content .\new.xml)
    
    $oldProgramNode = $oldXml.SelectSingleNode('//Program')
    $newProgramNode = $newXml.SelectSingleNode('//Program')
    
    foreach ($oldCategoryAItemNode in $oldProgramNode.SelectNodes('Item[Category = "Category A"]'))
    {
        $id = $oldCategoryAItemNode.SelectSingleNode('ID/text()').Value
        
        if ($id)
        {
            $matchingNewNode = $newProgramNode.SelectSingleNode("Item[ID='$id']")
            if ($matchingNewNode)
            {
                $null = $newProgramNode.RemoveChild($matchingNewNode)
            }
        }
    
        $newItemNode = $newXml.ImportNode($oldCategoryAItemNode, $true)
        $null = $newProgramNode.AppendChild($newItemNode)
    }
    
    $newXml.Save("$pwd\combined.xml")
    

    • Marked as answer by jack_90824590 Thursday, July 31, 2014 2:41 AM
    Thursday, July 31, 2014 2:12 AM
  • David - I will leave it to you to solve the long list of problems we have just discovered as well as finding out that the OP doesn't want help he just wants someone to do it for him so he can give it to a friend.

    It is all very doable but the rules are still very unclear.


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 2:17 AM
  • I get no errors, but it doesn't seem to do anything.  Nothing has changed in the xml files and I don't see a new combined.xml anywhere.  Am I missing something?

    Thursday, July 31, 2014 2:23 AM
  • As written, the combined.xml file gets created in whatever your current directory is (which is also where new.xml and old.xml were read from, when I tested it.) You could change those paths to whatever you like, but keep in mind that if you want to use a relative path in the call to Save(), prefix it with "$pwd\" like I did, not a ".\"

    .NET methods have no idea what PowerShell's current directory is; they use [Environment]::CurrentDirectory as the "." path instead.

    Thursday, July 31, 2014 2:35 AM
  • David - I will leave it to you to solve the long list of problems we have just discovered as well as finding out that the OP doesn't want help he just wants someone to do it for him so he can give it to a friend.

    It is all very doable but the rules are still very unclear.


    ¯\_(ツ)_/¯

    I am sorry you feel that way.  But I will do whatever I can to help.  I am here now because I was trying to help out and I have spent hours doing this. 

    Also PS is uncharted waters and quite difficult for me, I am trying very hard to understand it all.

    Also, I am not going to apologize for trying to help out some friends.

    Thanks for your time, goodbye.

    David, you got it working!  Thank you so much.  All i needed to change was the "print working directory" to c:\xml\ and it worked a treat. Excellent.

    I can manage the rest from here, thank you for your help, it is appreciated.

    Take Care.

    Thursday, July 31, 2014 2:38 AM
  • There's quite a bit of learning curve with PowerShell, but it's time very well invested. Once you get the hang of it (along with some C# and P/Invoke, if you're so inclined), there's virtually nothing you can't automate on a Windows system.  If there's not already a built-in PowerShell command or third-party module to accomplish a task, you have access to the entire .NET Framework, COM ecosystem, WMI, and Win32 API to write something yourself.
    Thursday, July 31, 2014 3:00 AM
  • When you posted that you had no interest in learning PowerShell it became clear you were running a scam to get someone to write a free script for you.  I have no respect for "gamers" who game use.

    You are not someone I would help.  YOU started this thread by pretending that you were trying to learn the technology necessary to do this.  You had no intention of doing anything but pulling a con job.


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 3:04 AM
  • There's quite a bit of learning curve with PowerShell, but it's time very well invested. Once you get the hang of it (along with some C# and P/Invoke, if you're so inclined), there's virtually nothing you can't automate on a Windows system.  If there's not already a built-in PowerShell command or third-party module to accomplish a task, you have access to the entire .NET Framework, COM ecosystem, WMI, and Win32 API to write something yourself.

    Yeah, I noticed that learning curve, it came in fast and took me by surprise. You know, I may consider taking the time to learn it, I love automating things and I have always used batch files, but Powershell seems to be much more powerful and I am kind of intrigued. I might try a beginners guide and at least the basics. I am enjoying video guides at the min, so might try Lynda or somewhere similar, see what they have. Thanks again for your time and for not being a prick.
    Thursday, July 31, 2014 3:56 AM
  • Check out the Microsoft Virtual Academy

    Getting Started with PowerShell 3.0 Jump Start
    http://www.microsoftvirtualacademy.com/training-courses/getting-started-with-powershell-3-0-jump-start

    Advanced Tools & Scripting with PowerShell 3.0 Jump Start
    http://www.microsoftvirtualacademy.com/training-courses/advanced-tools-scripting-with-powershell-3-0-jump-start
    Thursday, July 31, 2014 4:04 AM
  • When you posted that you had no interest in learning PowerShell it became clear you were running a scam to get someone to write a free script for you.  I have no respect for "gamers" who game use.

    You are not someone I would help.  YOU started this thread by pretending that you were trying to learn the technology necessary to do this.  You had no intention of doing anything but pulling a con job.


    ¯\_(ツ)_/¯


    You hit the nail on the head. I am a conman gamer who came to scam you. Look, I honestly thought it would be a simple, quick bit of code for you guys. I was mistaken and I appologize. I did attempt to do it myself, but didn't get very far. I thought I made that clerar, obviously I was wrong. I am not a "gamer" as you put it. Yes I'm building a retro games library, but I enjoy building it far more than playing it. When it is finished, I will move onto something else. Though I have to admit, on occasions I do like to relive my childhood and have a blast on Chuckie Egg, which I used to play on my Atari 800XE among others, but that was my favourite. Those were the days. For the record, despite what you thought of me, you were rude and obnoxious, I don't like that. So we can agree to dislike each other and leave it at that. Farewell.
    Thursday, July 31, 2014 4:12 AM
  • When you posted that you had no interest in learning PowerShell it became clear you were running a scam to get someone to write a free script for you.  I have no respect for "gamers" who game use.

    You are not someone I would help.  YOU started this thread by pretending that you were trying to learn the technology necessary to do this.  You had no intention of doing anything but pulling a con job.


    ¯\_(ツ)_/¯


    You hit the nail on the head. I am a conman gamer who came to scam you. Look, I honestly thought it would be a simple, quick bit of code for you guys.Stop wwining I was mistaken and I appologize. I did attempt to do it myself, but didn't get very far. I thought I made that clerar, obviously I was wrong. I am not a "gamer" as you put it. Yes I'm building a retro games library, but I enjoy building it far more than playing it. When it is finished, I will move onto something else. Though I have to admit, on occasions I do like to relive my childhood and have a blast on Chuckie Egg, which I used to play on my Atari 800XE among others, but that was my favourite. Those were the days. For the record, despite what you thought of me, you were rude and obnoxious, I don't like that. So we can agree to dislike each other and leave it at that. Farewell.

    Stop wining.  You care not to learn anything. You got your free code now just go away.


    ¯\_(ツ)_/¯

    Thursday, July 31, 2014 4:16 AM
  • Thanks for the links.  Appreciated
    Thursday, July 31, 2014 12:52 PM
  • Stop wining.  You care not to learn anything. You got your free code now just go away.


    ¯\_(ツ)_/¯

    x
    Thursday, July 31, 2014 12:54 PM