xpath query with backslashes return empty - excel

I am trying to select a single MSXML2 node in excel using XPath predicates. I am able to select it just fine when I supply a string without backslashes. But as soon as I try with a file path string, the expression returns nothing.
Here is my XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Directory>
<Document>
<Path/>
<Status/>
<Notes/>
</Document>
<Document>
<Path>C:\Users\Ivelin\Desktop\Workspace\Requests\File.xlsm</Path>
<Status>Started</Status>
<Notes/></Document>
<Document>
<Path>TEST</Path>
<Status>Started</Status>
<Notes/>
</Document>
</Directory>
This works:
Dim Stat As IXMLDOMNode
Dim strPath
strPath = "/Directory/Document[Path='TEST']/Status/text()"
MsgBox (strPath)
Set Stat = XDoc.SelectSingleNode(strPath)
MsgBox (Stat.NodeValue)
This returns null:
Dim Stat As IXMLDOMNode
Dim strPath
strPath = "/Directory/Document[Path='C:\Users\Ivelin\Desktop\Workspace\Requests\File.xlsm']/Status/text()"
MsgBox (strPath)
Set Stat = XDoc.SelectSingleNode(strPath)
MsgBox (Stat.NodeValue)
I tried different suggestions, double backslashes etc. but no luck. Since I am interested in file names/paths, I don't really have other option, but to use backslashes.
Any pointers on how to solve this are welcome.

I see nothing wrong with your xpath. Perhaps the error lies elsewhere. I used the following loading your xml from file; no problem.
Option Explicit
Public Sub test()
Dim xmlDoc As Object, item As Object
Set xmlDoc = CreateObject("MSXML2.DOMDocument") 'New MSXML2.DOMDocument60
With xmlDoc
.validateOnParse = True
.setProperty "SelectionLanguage", "XPath"
.async = False
If Not .Load("C:\Users\User\Desktop\Test.xml") Then
Err.Raise .parseError.ErrorCode, , .parseError.reason
End If
End With
Dim path As String
path = "/Directory/Document[Path='C:\Users\Ivelin\Desktop\Workspace\Requests\File.xlsm']/Status/text()"
Set item = xmlDoc.SelectSingleNode(path)
Debug.Print item.Text
End Sub

Related

Import XML data using Excel VBA

I'm trying to import specific data from and XML file to an Excel sheet.
The code I'm using is this.
Dim oXMLFile As New DOMDocument60
Dim books As IXMLDOMNodeList
Dim results() As String
Dim i As Integer, booksUBound As Integer
Dim book As IXMLDOMNode, title As IXMLDOMNode, author As IXMLDOMNode
oXMLFile.Load "C:\example.xml"
Set books = oXMLFile.SelectNodes("/OUT_MESSAGE/LINES/OUT_MESSAGE_LINE")
booksUBound = books.Length - 1
ReDim results(booksUBound, 1)
For i = 0 To booksUBound
Set book = books(i)
Set title = book.SelectSingleNode("C00")
If Not title Is Nothing Then results(i, 0) = title.Text
Next
Dim wks As Worksheet
Set wks = ActiveSheet
wks.Range(wks.Cells(1, 1), wks.Cells(books.Length, 2)) = results
Which works with this XML
<?xml version="1.0" encoding="UTF-8"?>
<OUT_MESSAGE>
<LINES>
<OUT_MESSAGE_LINE>
<C00>1231231</C00>
<C01>3213213</C01>
</OUT_MESSAGE_LINE>
<OUT_MESSAGE_LINE>
<C00>1231234</C00>
<C01>3213214</C01>
</OUT_MESSAGE_LINE>
</LINES>
</OUT_MESSAGE>
My problem is that my XML file looks like this.
<?xml version="1.0" encoding="UTF-8"?>
<OUT_MESSAGE xmlns="urn:randomaddress-com:schema:test_out_message" xmlns:xsi="http://www.randomurl.com/123">
<LINES>
<OUT_MESSAGE_LINE>
<C00>1231231</C00>
<C01>3213213</C01>
</OUT_MESSAGE_LINE>
<OUT_MESSAGE_LINE>
<C00>1231234</C00>
<C01>3213214</C01>
</OUT_MESSAGE_LINE>
</LINES>
</OUT_MESSAGE>
Which I originally thought I could simply get to work by replacing
Set books = oXMLFile.SelectNodes("/OUT_MESSAGE/LINES/OUT_MESSAGE_LINE")
With
Set books = oXMLFile.SelectNodes("/OUT_MESSAGE xmlns='urn:randomaddress-com:schema:test_out_message' xmlns:xsi='http://www.randomurl.com/123'/LINES/OUT_MESSAGE_LINE")
But that gives me a runtime error.
If anyone know what changes I have to do to the original code that would be much appreciated.
This worked for me:
Dim xDoc, nodes, oNode
Set xDoc = CreateObject("MSXML2.DOMDocument.6.0")
'Note: added an `x=` to the default namespace so we can reference it later
xDoc.setProperty "SelectionNamespaces", _
"xmlns:x='urn.randomaddress.com.schema.test_out_message'"
xDoc.LoadXML Sheet2.Range("A4").Value 'load XML from sheet
'use the "x" prefix we added above
Set nodes = xDoc.SelectNodes("/x:OUT_MESSAGE/x:LINES/x:OUT_MESSAGE_LINE")
Debug.Print nodes.Length ' = 1
For Each oNode In nodes
Debug.Print oNode.SelectSingleNode("x:C00").nodeTypedValue
Debug.Print oNode.SelectSingleNode("x:OBJSTATE").nodeTypedValue
'etc
Next oNode
using this XML:
<?xml version="1.0"?>
<OUT_MESSAGE xmlns="urn.randomaddress.com.schema.test_out_message"
xmlns:xsi="http://www.randomurl.com/123">
<LINES>
<OUT_MESSAGE_LINE>
<C00>321312</C00>
<C01>12312312</C01>
<OBJSTATE>Posted</OBJSTATE>
<OBJEVENTS>Accept^Reject^</OBJEVENTS>
<STATE>Posted</STATE>
</OUT_MESSAGE_LINE>
</LINES>
</OUT_MESSAGE>

XML and XPath handling in VBA

I'm trying to parse a XML into a spreadsheet using VBA, and for some reason I can't to the node that I want using XPath, here how my XML looks like:
<?xml version="1.0" encoding="UTF-8"?>
<cteProc xmlns="http://www.somesite.com" versao="3.00">
<CTe xmlns="http://www.somesite.com">
<infCte Id="an id" versao="3.00">
<ide>
<cUF>23</cUF>
<cCT>00000557</cCT>
<CFOP>6932</CFOP>
<natOp>some text </natOp>
<mod>57</mod>
</ide>
<compl>
<xObs>TEXT</xObs>
</compl>
</infCte>
</CTe>
</cteProc>
I'm trying to get at least to the ide node, so I can loop over the rest and get the information I want.
My code looks like this:
Public Sub parseXml()
Dim oXMLFile As MSXML2.DOMDocument60
Dim nodes As MSXML2.IXMLDOMNodeList
path2 = "C:\Users\me\Desktop\adoc.xml"
Set oXMLFile = New MSXML2.DOMDocument60
oXMLFile.Load (path2)
Set nodes = oXMLFile.DocumentElement.SelectNodes("/CTe")
So I tried to print the length of the nodes, I get this:
debug.print nodes.length
> 0
if I loop over like this:
Public Sub parseXml()
Dim oXMLFile As MSXML2.DOMDocument60
Dim nodes As MSXML2.IXMLDOMNodeList
Dim node As MSXML2.IXMLDOMNode
path2 = "C:\Users\me\Desktop\adoc.xml"
Set oXMLFile = New MSXML2.DOMDocument60
oXMLFile.Load (path2)
Set nodes = oXMLFile.DocumentElement.ChildNodes
For Each node In nodes
Debug.Print node.BaseName
Next node
I get this:
> CTe
So, If I do a giant loop I can get the information I want, but I think there must be a simpler sulution for this.
Since your XML uses namespaces, XPath also needs to deal with namespaces.
The following works for me using your XML:
Public Sub parseXml()
Dim oXML As MSXML2.DOMDocument60
Dim oNodes As MSXML2.IXMLDOMNodeList
Dim oItem As MSXML2.IXMLDOMNode
Dim path2 As String
path2 = "P:\adoc.xml"
Set oXML = New MSXML2.DOMDocument60
oXML.Load path2
oXML.setProperty "SelectionLanguage", "XPath"
oXML.setProperty "SelectionNamespaces", "xmlns:ssc=""http://www.somesite.com"""
Set oNodes = oXML.DocumentElement.SelectNodes("ssc:CTe")
For Each oItem In oNodes
MsgBox oItem.nodeName
Next
End Sub
There using
oXMLFile.setProperty "SelectionNamespaces", "xmlns:ssc=""http://www.somesite.com"""
I define a prefix ssc for the namespace
http://www.somesite.com.
The scc is my own choice (somesite.com). This prefix is needed for the XPATH in selectNodes method to work properly.
If you don't want defining the namespace, you would must use the local-name() XPath function. For example:
Set oNodes = oXML.DocumentElement.SelectNodes("*[local-name() = 'CTe']")

Create new XML Element as a Parent Node of Existing Element

I would like to create a new XML element to make my existing XML node as a child node of this new element. The structure of my current XML file is:
<?xml version="1.0" encoding="utf-8"?>
<component>
<type name="A"></type>
<type name="B"></type>
</component>
My idea is to create new element "masterType" and make it as a parent node of existing "type" element.
<?xml version="1.0" encoding="utf-8"?>
<component>
<masterType>
<type name="A"></type>
<type name="B"></type>
</masterType>
</component>
My question is, how can I make this new element as a parent node of my existing xml node? What happens if I used insertBefore(), the "masterType" already ends before the element "type".
<?xml version="1.0" encoding="utf-8"?>
<component>
<masterType>
</masterType>
<type name="A"></type>
<type name="B"></type>
</component>
Here's my code
Dim fileName As String
fileName = ActiveSheet.OLEObjects("TextBox1").Object.Text
XMLFileName = fileName
Dim Found As Boolean
Dim docXMLDOM As DOMDocument
Dim nodeType As IXMLDOMNodeList
Dim nodElement As IXMLDOMElement
Dim nodNewElement As IXMLDOMElement
Dim nodReference As IXMLDOMElement
Set docXMLDOM = New DOMDocument
docXMLDOM.Load XMLFileName
Set nodeType = docXMLDOM.getElementsByTagName("type")
For Each nodElement In nodeType
If nodElement.Attributes.getNamedItem("name").Text = "A" Then
Set nodReference = nodElement
Set nodNewElement = docXMLDOM.createElement("masterType")
nodElement.ParentNode.InsertBefore nodNewElement, nodElement
Exit For
End If
Next
docXMLDOM.Save XMLFileName
Simplified example:
Sub AddParentNode()
Dim docXMLDOM As MSXML2.DOMDocument60
Dim els As IXMLDOMNodeList
Dim masterEl As IXMLDOMElement
Dim el As IXMLDOMElement
Set docXMLDOM = New MSXML2.DOMDocument60
docXMLDOM.LoadXML Range("A1").Value 'for testing
Debug.Print "*** Before ***"
Debug.Print docXMLDOM.XML
Set els = docXMLDOM.getElementsByTagName("type")
If els.Length > 0 Then
'create the new parent element
Set masterEl = docXMLDOM.createElement("masterType")
els(1).ParentNode.appendChild masterEl
End If
'append each "type" element into the new parent node
For Each el In els
masterEl.appendChild el.CloneNode(True)
el.ParentNode.RemoveChild el
Next
Debug.Print "*** After ***"
Debug.Print docXMLDOM.XML
End Sub

Using Xpath in Excel to read a child node's attribute

I am having problems getting the value of "Data Import" from the source_lvl2 child node. In Excel, I get a run-time error of 91, "Object variable or With block variable not set"
I can't see what I'm doing wrong - any advice?
VBA
Sub TestXML()
Dim XDoc As Object
Set XDoc = CreateObject("MSXML2.DOMDocument")
XDoc.async = False: XDoc.validateOnParse = False
XDoc.Load ("C:\171215-000438_1513346972.xml")
Set lists = XDoc.SelectNodes("/archive/primary_rnw_contact/source/source_lvl2")
Debug.Print lists(0).Attributes(0).Text
Set XDoc = Nothing
End Sub
XML
EXTRA CODE
<?xml version="1.1" encoding="UTF-8"?>
<archive product="RightNow" version="4.0" build="17.8.0.1.0.248" label="Archived Incident">
You could try the following (not tested):
Dim xmap As XmlMap
Dim k as Long
Dim oMyNewColumn As ListColumn
oMyList As ListObject
' Delete all previous XML maps
k = ThisWorkbook.XmlMaps.Count 'Detect all XML maps
For i = 1 To k
ThisWorkbook.XmlMaps(i).Delete
Next i
...
Set xmap = ThisWorkbook.XmlMaps.Add("some_file.xml")
...
Set oMyNewColumn = oMyList.ListColumns.Add
oMyList.ListColumns(3).XPath.SetValue xmap, "/archive/primary_rnw_contact/source/source_lvl2"

How to create an attribute with specific value if it doesn't exist - VBA

Sorry for my bad English, but i'll try to describe my problem right. I have a code in VBA. Here it is:
Sub TestXML()
Dim doc As New DOMDocument
Const filePath As String = "D:\Test3.xml"
Dim isLoaded As Boolean
isLoaded = doc.Load(filePath)
If isLoaded Then
Dim oAttributes As MSXML2.IXMLDOMNodeList
Set oAttributes = doc.getElementsByTagName("Operation")
Dim attr As MSXML2.IXMLDOMAttribute
Dim node As MSXML2.IXMLDOMElement
Dim tdate As String
tdate = Format(Now(), "yyyy-mm-dd")
For Each node In oAttributes
For Each attr In node.Attributes
If attr.Name = "Client" Then
If attr.Value <> "UL" Then
attr.Value = "UL"
End If
ElseIf attr.Name = "Date" Then
If attr.Value <> "tdate" Then
attr.Value = tdate
End If
End If
Next attr
Next node
doc.Save filePath
End If
End Sub
Question is - How can i create attribute "Client" with value "UL" in element "Operation" only if it doesn't exist?
Here is a .xml file example with which i working:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Document>
<Operations>
<Operation Date="2018-11-06" Client="UL"/>
<Operation Date="2018-11-06" Client="UL"/>
<Operation Date="2018-11-06"/>
</Operations>
</Document>
Thanks!
Try to read the attribute node, if it does not exist create it:
For Each node In oAttributes
If (node.getAttributeNode("Client") Is Nothing) Then
'// add missing attrib
node.setAttribute "Client", "UL"
End If
Your current code seems to want all elements to have Client=UL, to accomplish that simply:
For Each node In oAttributes
node.setAttribute "Client", "UL"
Next node
Which will overwrite or create the attribute as needed.

Resources