0

I have a number of identical XML reports stored in excel. In these reports, each item in one of the tables in each report a unique ID relating that item to a specific entity. Each entity has multiple items associated with it. Essentially, the structure of the macro I am looking for is as follows:

1) Excel searches for the ENTITY title tag in the XML report and stores the value contained between the end of the left tag (i.e. >) and the beginning of the right tag (i.e. <).

2) Excel searches for the ITEM title tag (the match must be exact).

3) Excel selects the row below the ITEM title tag and moves it down, and inserts the stored ENTITY value to the now empty cell above.

4) Excel continues to do this for all instances of the ITEM tag until it reaches another ENTITY title tag, at which point it loops.

I am thinking I would just need two loops, the ENTITY loop taking priority over the ITEM loop so that it is constantly looking for a new ENTITY. Otherwise I have no idea how it will know to start looking for a new entity.

Any help would be appreciated, thanks!

EDIT:

For reference the XML looks like this:

<Results xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Reference>{REFERENCE-HERE}</Reference>
  <FillerTags>Filler</FillerTags>
  <entity>
    <entityName>ABC</entityName>
    <entityId>012345</entityId>
  </entity>
  <Items>
    <Item>
      <FillerTagsAgain>Filler2</FillerTagsAgain>
      <FillerTagsAgain>Filler2</FillerTagsAgain>
      <FillerTagsAgain>Filler2</FillerTagsAgain> 
     </Item>
     <AnotherItem> 
       <FillerTagsAgain>Filler2</FillerTagsAgain>
       <FillerTagsAgain>Filler2</FillerTagsAgain>
       <FillerTagsAgain>Filler2</FillerTagsAgain> 
     </AnotherItem>
   </Items>

and would be modified to look like this:

<Results xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Reference>{REFERENCE-HERE}</Reference>
  <FillerTags>Filler</FillerTags>
  <entity>
    <entityName>ABC</entityName>
    <entityId>012345</entityId>
  </entity>
  <Items>
    <Item>
      <entityId>012345</entityId>
      <FillerTagsAgain>Filler2</FillerTagsAgain>
      <FillerTagsAgain>Filler2</FillerTagsAgain>
      <FillerTagsAgain>Filler2</FillerTagsAgain> 
     </Item>
     <AnotherItem> 
       <entityId>012345</entityId>
       <FillerTagsAgain>Filler2</FillerTagsAgain>
       <FillerTagsAgain>Filler2</FillerTagsAgain>
       <FillerTagsAgain>Filler2</FillerTagsAgain> 
     </AnotherItem>
   </Items>
 <entity>
    .
    .
    .

I have tried to start by defining some variables and trying to set up a basic structure:

Dim entity As String
Dim item As String
Dim i As Long
Dim j As Long
Dim wb As Workbook
Dim LastEntity As Long
Dim LastItem As Long

LastEntity = Cells.CountIf(Range("A1:A438486")), "<Entity>")

With ActiveSheet
    For i = 1 To LastEntity
    Cells.Find(What:="ENTITY(i)", After:=ActiveCell, LookIn:=xlFormulas, _
               MatchCase:=False, SearchFormat:=False).Activate
        For j = 1 To LastItem

The first place I am stuck then is as follows: How do I tell VBA to cycle through all the values that come up when using the 'Find' function. For example, if appears 50 times how do I tell VBA to start with the first entity, then begin the For j = 1 To LastItem loop? Is the setup above anywhere close to correct?

Community
  • 1
  • 1
114
  • 876
  • 3
  • 25
  • 51
  • 1
    I do not think this code does what you think it does. **What this code does:** Literally selects A75, A76, then inserts a row, then scrolls down, then selects A52, copies, scrolls down a bit more, selects A76 again, Selects A55, Copies it, then selects A76 and then pastes to A76. This lacks **all** of the components necessary to be a viable solution. You should use a proper XML/DOM parser, and load the XML, rather than the Excel-interpretation of XML. – David Zemens May 09 '14 at 15:28
  • @David I agree with you about the code above, I suppose it was meant to show that I am not really sure how to implement the steps I wrote out in VBA. Would using an XML parser allow me to insert IDs in the same way automatically? – 114 May 09 '14 at 15:32
  • Most likely. Without seeing any XML and without any code of your own it is really not very possible to help you -- this site is to assist with *specific* problems implementing code, not a general "Teach me how to do XYZ". I would recommend to use the MSXML, v6.0 library in VBA. There is a lot of documentation and examples about parsing XML and working with XML. Once you have made some attempts to use these methods, revise your question and show what you ahve tried, you will be more likely to get response that way. Cheers. – David Zemens May 09 '14 at 15:44
  • @David I have updated my post to include some XML and a (slightly) better start to the macro. Would you happen to know how to address the simpler question I have put in place of my broader question before? – 114 May 13 '14 at 16:02
  • You are barking up the wrong tree trying to use string functions to parse XML. It is an exercise in futility, although it can kind of be done. Again, I suggest using MSXML DOM parser to do this. Let me see if I can get it to do what you want though. – David Zemens May 13 '14 at 16:31
  • @David I have been taking a bit of time to look over MSXML DOM, but in this case all I am really looking to do is move around a few as though they are strings within Excel cells. That said, I do not know enough about MSXML DOM to know how much more efficient it would be. Hopefully I can learn enough about MSXML DOM to avoid this method for more complex XML tasks. – 114 May 13 '14 at 16:48

1 Answers1

1

Copy your XML in to a plain text file using Notepad/etc. Then save as an XML file making sure that file type is "All files ."

enter image description here

Then close that XML file.

The following example illustrates how to parse and modify the XML per your question. I am not going to show you how to do this using Excel worksheets, that is frankly an objectively wrong way of manipulating XML data.

This code successfully modifies the XML from your initial state to the described example output.

Option Explicit
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub ParseResults()
'Requires reference to Microsoft XML, v6.0
'Requires referenc to Microsoft Scripting Runtime
Dim xmlFilePath$, newFilePath$
Dim DOM As MSXML2.DOMDocument
Dim entity As IXMLDOMNode
Dim fso As Scripting.FileSystemObject

'# Define the file you are going to load as XML
xmlFilePath = "C:\users\david_zemens\desktop\results.xml"

'# Define an output path for where to put the modified XML
newFilePath = "C:\users\david_zemens\desktop\updated_results.xml"

'# Create our DOM object
Set DOM = CreateObject("MSXML2.DOMDocument")

'# Load the XML file
DOM.Load xmlFilePath

'# Wait until the Document has loaded
Do
    Sleep 250
Loop Until DOM.ReadyState = 4

'# Get the entityID node
Set entity = DOM.DocumentElement.getElementsByTagName("entityId")(0)

'# Call a subroutine to append the entity to "Item" tags
AppendEntity DOM, "Item", entity
'# Call a subroutine to append the entity to "AnotherItem" tags
AppendEntity DOM, "AnotherItem", entity

'## Create an FSO to write the new file
Set fso = CreateObject("Scripting.FileSystemObject")

'## Attempt to write the new/modified XML to file
On Error Resume Next
fso.CreateTextFile(newFilePath, True, True).Write DOM.XML
If Err Then
    '## Print the new XML in the Immediate window
    Debug.Print DOM.XML
    MsgBox "Unable to write to " & newFilePath & " please review XML in the Immediate window in VBE.", vbInformation
    Err.Clear
End If
On Error GoTo 0

'Cleanup
Set DOM = Nothing
Set fso = Nothing
Set entity = Nothing

End Sub

Sub AppendEntity(DOM As Object, tagName As String, copyNode As Object)
'## This subroutine will append child node to ALL XML Nodes matching specific string tag.
Dim itemColl As IXMLDOMNodeList
Dim itm As IXMLDOMNode

'# Get a collection of all elements matching the tagName
Set itemColl = DOM.DocumentElement.getElementsByTagName(tagName)

'# Iterate over the collection, appending the copied node
For Each itm In itemColl
    If itm.HasChildNodes Then
        '# Insert this node before the first child node of Item
        itm.InsertBefore copyNode.CloneNode(True), itm.FirstChild
    Else
        '# Append this node to the Item
        itm.appendChild copyNode.CloneNode(True)
    End If
Next

Set itm = Nothing
Set itemColl = Nothing

End Sub

UPDATE

Added a busy/waiting loop to (hopefully) ensure that DOM has fully loaded the XML. On a larger file than the simple example, this may take a few seconds to load, and that could raise errors if you attempt to parse the XML before it's ready.

David Zemens
  • 53,033
  • 11
  • 81
  • 130
  • This might seem silly, but I keep getting an error with the DOM.Document.Element.getElementsByTagName("entity")(0) line. The tag is - is there any reason why it wouldn't be picked up by that? – 114 May 13 '14 at 20:12
  • what error? Also, make sure it is not a typo, it is `.DocumentElement` not `.Document.Element`. – David Zemens May 13 '14 at 20:13
  • 1
    My mistake, the code states the correct version. The error is: Object variable / with block variable not set, which suggests to me that it is having trouble finding the tag, but I am not sure. – 114 May 13 '14 at 20:18
  • Can you please update your question (above) to include the *exact* code you are currently using? I have an idea about what it could be, but I want to make sure everything else looks good, first. I will check back in a few hours. – David Zemens May 13 '14 at 20:22
  • I have not made any changes to the code except for changing every instance of 'entity' to some 'a' and every instance of 'entityId' to 'b'. I did leave AppendEntity the same though since it just specifies a function. I have double checked to make sure that every other instance has been changed correctly so that I am reasonably confident that won't be the issue (I hope). – 114 May 13 '14 at 20:28
  • Did you also change the assignment of `xmlFilePath` and `newFilePath` to refer to location on your local drive? – David Zemens May 13 '14 at 21:12
  • OK. see revision above, I think this is the most likely culprit, that the document had not finished loading the XML. – David Zemens May 13 '14 at 21:18
  • Also note case-sensitivity, "entity" is not the same as "Entity", "A" is not the same as "a", etc. – David Zemens May 13 '14 at 21:32
  • Thanks, the updated code seems to be missing 'Wend' though. Where should the 'while' statement end? As for case-sensitivity I checked and the cases I'm using are correct - I hadn't thought about that before though. – 114 May 13 '14 at 21:36
  • It is now claiming that Doc is not defined – 114 May 13 '14 at 21:38
  • Typo on my part... Loop Until DOM.REadystate = 4. – David Zemens May 13 '14 at 21:39
  • Sorry about that, I realized it right before coming back to delete that comment. However, we arrive at the same error as before. – 114 May 13 '14 at 21:40