VBA Macro, get URL from given range loop and pull XML node - excel

I have this code that is set up to get data ZIP code (single node) from an XML URL. However, I actually have a list of URLs in Sheet1, Column B that I need to loop through until all the data is extracted.
I dont want to have to update the code each time individually per URL. There are thousands... How would i be able to do that?
Here's an example of a working code for a single URL:
Sub test1()
Dim xmlDocument As MSXML2.DOMDocument60
Dim URL As String
Dim node As Object
Set xmlDocument = New DOMDocument60
URL = Sheets("Sheet1").Range("b2").Value
'Open XML page
Set xmlDocument = New MSXML2.DOMDocument60
xmlDocument.async = False
xmlDocument.validateOnParse = False
xmlDocument.Load URL
Dim nodeId As IXMLDOMNode
Dim nodeId2 As IXMLDOMNode
Set nodeId = xmlDocument.SelectSingleNode("/ZipCodeLookupResponse/Address/Zip5")
Set nodeId2 = xmlDocument.SelectSingleNode("/ZipCodeLookupResponse/Address/Zip4")
If Not nodeId Is Nothing Then
Sheets("fy2016").Range("e2").Value = nodeId.Text & " " & nodeId2.Text
Else
Sheets("fy2016").Range("e2").Value = "'ZIP code' was not found."
End If
End Sub

Assuming your code works you want something like a For Loop over all the urls. Move your document outside of the loop and load it inside the loop. I use an array to store the urls read in from the sheet for faster handling. Your construct wasn't handling any errors on parse so I have commented out related lines.
Not tested.
Option Explicit
Public Sub test1()
Dim xmlDocument As MSXML2.DOMDocument60, URLs(), i As Long
Dim node As Object, nodeId As IXMLDOMNode, nodeId2 As IXMLDOMNode
Set xmlDocument = New DOMDocument60
URLs = ThisWorkbook.Worksheets("Sheet1").Range("B2:B1000").Value
Set xmlDocument = New MSXML2.DOMDocument60
xmlDocument.async = False
' xmlDocument.validateOnParse = False
For i = LBound(URLs, 1) To UBound(URLs, 1)
xmlDocument.Load URLs(i, 1)
Set nodeId = xmlDocument.SelectSingleNode("/ZipCodeLookupResponse/Address/Zip5")
Set nodeId2 = xmlDocument.SelectSingleNode("/ZipCodeLookupResponse/Address/Zip4")
If Not nodeId Is Nothing Then
ThisWorkbook.Worksheets("fy2016").Cells(i + 1, "E").Value = nodeId.Text & " " & nodeId2.Text
Else
ThisWorkbook.Worksheets("fy2016").Cells(i + 1, "E").Value = "'ZIP code' was not found."
End If
Set nodeId = Nothing: Set nodeId2 = Nothing
Next
End Sub

Related

VBA - Late Binding vs. Early Binding Error - Difference in xml responses

I wrote some code to GET a response from a server using early binding (first statement when mbEARLY_BINDING_FSO = True
Option Explicit
Option Private Module
#Const mbEARLY_BINDING_FSO = False
Private Const msMODULE_NAME As String = "Controls"
Public Sub refresh_database()
Const sPROC_NAME As String = "refresh_database()"
If Not gbDEBUG Then On Error GoTo errExitLL
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'LOWER LEVEL PROCEDURE
'Comments: Refresh the database from call to app.longtrend.com/api/seach?map=tickers
'Agurments: None
'Dependencies: None
'Returns: JSON object of available companies listed to Company List sheet
'----------------------------------------------------------------------------------------------------------------------
Dim base_url As String
Dim successMsg As Variant
Dim Json As Object
Dim account_key As Variant
Dim dict_key As Variant
Dim item As Variant
Dim sheet_ticker As Worksheet
Dim lrow As Long
Dim lcol As Long
Dim rng As Range
#If mbEARLY_BINDING_FSO Then
Dim xml_obj As MSXML2.XMLHTTP60
Set xml_obj = New MSXML2.XMLHTTP60
#Else
Dim xml_obj As Object
Set xml_obj = CreateObject("MSXML2.XMLHTTP.6.0")
#End If
Set sheet_ticker = Sheets("Admin_Control")
UserFormProgress.Display 5, False
UserFormProgress.SetText "Checking stuff XX ..."
Application.Wait Now + #12:00:01 AM#
base_url = "https://app.longtrend.com/api/search?map=tickers"
'Open a new get request using URL
xml_obj.Open bstrMethod:="GET", bstrURL:=base_url
xml_obj.send
'set up the object and parse the response
Set Json = JsonConverter.ParseJson(xml_obj.responseText)
' Status code router - 200 is Success, all else will print error in range("STATUS") and exit sub
If xml_obj.Status <> 200 Then
With Range("STATUS")
.Value = xml_obj.Status & ": " & Json("Error")
.Font.Color = RGB(255, 143, 143)
End With
Application.ScreenUpdating = True
End
End If
'Parse Json object
Dim i As Long
Dim key As Variant
i = rng.Row + 1
For Each key In Json
sheet_ticker.Cells(i, rng.Column) = key
sheet_ticker.Cells(i, rng.Column + 1) = Json(key)("name")
sheet_ticker.Cells(i, rng.Column + 2) = Json(key)("sector")
sheet_ticker.Cells(i, rng.Column + 3) = Json(key)("industry")
sheet_ticker.Cells(i, rng.Column + 4) = Json(key)("marketCap")
sheet_ticker.Cells(i, rng.Column + 5) = Json(key)("lastFY")
i = i + 1
Next
Exit Sub
errExitLL:
Application.ScreenUpdating = True
ErrorHandling.LowLevel msMODULE_NAME, sPROC_NAME, "url", base_url, "last row", lrow
End Sub
With early binding, my xml_obj response is as exptected. The responseText stores all values to be parsed in the JSON converter. Now, prior to release I'd like to set to late binding. I've created the object as shown in the second statement however the responseText in the locals window says: this method cannot be called until the send method has been called. The xml_obj is sent prior to this local response.
I have tried the following so far:
Set xml_obj = CreateObject("Microsoft.XMLHTTP")
Set xml_obj = CreateObject("MSXML2.XMLHTTP60")
Set xml_obj = CreateObject("MSXML2.XMLHTTP.6.0")
Set xml_obj = CreateObject("MSXML2.ServerXMLHTTP")
To no avail! An error occur either when I attempt to create the object CreateObject(XX) and there is no associated ActiveX available, or as mentioned above, the response request isn't correct once the request is sent. I'm not sure what I'm missing as this should be a simple enough activity. Any help is much appreciated.
Running Office 365 (build 14228) for Windows (64 bit, VBA7)
Thanks,
Scott
If you don't specify a value, then the third argument to Open (asynchronous) defaults to True, so you should pass False there.
If you run asynchronously your code will not wait until the response is complete.

Lotusscript: Retrieve the images in the body of an email

In my agent, I try to retrieve all the files that are in the current email. My attached code works fine, except for the images in the body of the email. I manage to retrieve all the files and images that were attached to the email except the pictures that were copied and pasted in the middle of the email text. Here is my code:
Dim session As New NotesSession
Dim doc As NotesDocument
Dim db As NotesDatabase
Dim item As Variant
Dim CurrentDocColl As NotesDocumentCollection
Set db = Session.Currentdatabase
Set CurrentDocColl = db.Unprocesseddocuments
Set doc = CurrentDocColl.Getfirstdocument
While Not doc Is Nothing
Set item = doc.GETFIRSTITEM("Body")
If doc.HasEmbedded Then
ForAll attachment In item.EmbeddedObjects
Call attachment.ExtractFile (pathname & "\" & attachment.Name)
End ForAll
End If
Set doc=CurrentDocColl.Getnextdocument(doc)
Wend
How can I retrieve these images?
Thank you very much for your help
I have an agent that does a lot of that, but it's not short. What you have to do is run the document through an XML DomParser, walk down the DOM tree and when you find a node with "JPEG" or "PNG" in the name (the inline images themselves), stream the data to a file and save it. The code is combination of an agent I found online (which I couldn't find again, otherwise I would give credit) and work I've done. You won't be able to copy/paste this sample code and expect it to work, I've removed things (like declaring variables and supporting functions) for brevity.
Sub Initialize
Dim dxlExp As NotesDXLExporter
Set dxlExp = s.CreateDXLExporter
Call dxlExp.setInput(Doc)
Set DomParser=s.CreateDOMparser()
Call DomParser.Setinput(dxlExp)
Dim dxlImp As NotesDXLImporter
Set dxlImp = s.Createdxlimporter()
Call dxlImp.Setinput(domParser)
Call dxlImp.SetOutput(db)
On Event PostDomParse From DomParser Call DomInputProcessed
Call dxlExp.Process
End Sub
Sub DomInputProcessed(DomParser As NotesDomParser)
Dim DomNode As NotesDomNode
Set DomNode = DomParser.Document
Call walkTree(DomParser, DomNode)
Exit Sub
End Sub
Sub walkTree (DomParser As NotesDOMParser, node As NotesDOMNode)
Select Case node.NodeType
Case DOMNODETYPE_DOCUMENT_NODE: ' If it is a Document node
domParser.Output( "<?xml version='1.0' encoding='utf-8'?>"+LF )
Set child = node.FirstChild ' Get the first node
Dim numChildNodes As Integer
numChildNodes = node.NumberOfChildNodes
While numChildNodes > 0
Set child = child.NextSibling ' Get next node
numChildNodes = numChildNodes - 1
Call walkTree(DOMParser, child)
Wend
Case DOMNODETYPE_DOCUMENTTYPE_NODE: ' It is a <!DOCTYPE> tag
domParser.Output("<!DOCTYPE "+ node.NodeName+ ">" + LF)
Case DOMNODETYPE_TEXT_NODE: ' Plain text node
value = xmlReplace(node.NodeValue)
domParser.Output(value)
Case DOMNODETYPE_ELEMENT_NODE: ' Most nodes are Elements
Select Case node.NodeName
Case "jpeg"
Dim jpegfile As String
' Step 1, write the MIME file
Dim base64node As NotesDOMNode
Set base64Node = node.Firstchild
Dim base64Out As NotesStream
Set base64Out = s.createStream()
Dim bytesWritten As Long
bytesWritten = base64Out.Writetext(base64Node.NodeValue)
' Step 2, Read the MIME file and decode it.
Set db=s.currentdatabase
Set doc=db.createDocument()
Set m=doc.Createmimeentity("Image1")
Call m.setContentFromText(base64Out, "image/jpeg", 1727)
Call m.Decodecontent()
Dim JPEGOut As NotesStream
Set JPEGOut = s.createStream()
jpegFile = RandomFileName(baseDir, ".jpg")
JPEGOut.open(jpegFile)
Call m.Getcontentasbytes(JPEGOut, True)
Call JPEGOut.Close()
attachmentNamesStr = attachmentNamesStr + jpegFile + "~"
' Step 3, remove the jpeg and its child node
' We do this by just not sending anything to the DomParser output.
Case "png"
' Same as JPEG except it's PNG.
End Select
End Select 'node.NodeType
End If 'Not node.IsNull
End Sub

Specific node in XML file use VBA

I want to replace data in a XML file using VBA. The XML data looks like this:
<MarkGrades>
<Grade MarkGrade="0" Label="3OP" TestBins="1,-1">0</Grade>
<Grade MarkGrade="2" Label="GOOD" TestBins="2">2</Grade <Grade>
<Grade MarkGrade="4" Label="FU" TestBins="31,3135,3136,3312,">4</Grade>
<Grade MarkGrade="3" Label="PA" TestBins="4016,4022,4029">3</Grade>
<Grade MarkGrade="5" Label="OS" TestBins="12,13,20012,20013">5</Grade>
</MarkGrades>
I want to replace data in node TestBins. If MarkGrade="4" or Label="FU" then replace data in TestBins from "2" to "something". But I can't seem to figure out how to specify that:
Dim doc As Object
Set doc = CreateObject("MSXML2.DOMDocument")
Dim isLoaded As Boolean
Dim i As Integer
Dim filePath As Variant
filePath = "C:\Users\xxxx\Desktop\Splitter\test.xml"
isLoaded = doc.Load(filePath)
i = 0
If isLoaded Then
Dim Grade As msxml2.IXMLDOMNodeList
Set Grade = doc.getElementsByTagName("Grade")
Dim attr As msxml2.IXMLDOMAttribute
Dim node As msxml2.IXMLDOMElement
For Each node In Grade
For Each attr In node.Attributes
If attr.Name = "TestBins" Then
i = i + 1
If i = 3 Then
attr.Value = SplitterMark.TextBox3.Value
ElseIf i = 4 Then
attr.Value = SplitterMark.TextBox4.Value
ElseIf i = 5 Then
attr.Value = SplitterMark.TextBox5.Value
End If
End If
Next attr
Next node
I tried to count if found TestBins count +1
That is working good. But can use with only this file because other file it is not common. like this:
<MarkGrades>
<Grade MarkGrade="5" Label="OS" TestBins="12,13,20012,20013">5</Grade>
<Grade MarkGrade="0" Label="3OP" TestBins="1,-1">0</Grade>
<Grade MarkGrade="4" Label="FU" TestBins="31,3135,3136,3312,">4</Grade>
<Grade MarkGrade="2" Label="GOOD" TestBins="2">2</Grade <Grade>
<Grade MarkGrade="3" Label="PA" TestBins="4016,4022,4029">3</Grade>
</MarkGrades>
Any ideas?
You could use xpath to identify the appropriate nodes and setAttribute for the value setting. You need to correct your xml though.
Option Explicit
Public Sub test()
Dim xmlDoc As Object
Set xmlDoc = CreateObject("MSXML2.DOMDocument")
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 elem As Object
For Each elem In xmlDoc.SelectNodes("//Grade[#MarkGrade='4' or #Label='FU' and #TestBins]")
elem.setAttribute "TestBins", "Banana"
Next
Debug.Print xmlDoc.XML
End Sub
Corrected xml line:
<Grade MarkGrade="2" Label="GOOD" TestBins="2">2</Grade>

VBA and OneNote. Moving a page in another section: Error on OneNote.UpdateContentPage method

I wrote a Subroutine to move a page in a different section compared to the original one but it does't work! May someone help me?
Private Sub CreateInNewSection(onote As OneNote.Application, pageXML As String, newSecId As String, title As String)
Dim pDoc As MSXML2.DOMDocument60
Set pDoc = New MSXML2.DOMDocument60
If pDoc.LoadXML(pageXML) Then
Dim cNodes As MSXML2.IXMLDOMNodeList
Dim fNodes As MSXML2.IXMLDOMNodeList
Dim iNodes As MSXML2.IXMLDOMNodeList
Dim cNode As MSXML2.IXMLDOMNode
Dim fNode As MSXML2.IXMLDOMNode
Dim iNode As MSXML2.IXMLDOMNode
soapNS = "xmlns:one='http://schemas.microsoft.com/office/onenote/2013/onenote'"
pDoc.setProperty "SelectionNamespaces", soapNS
Set cNodes = pDoc.DocumentElement.SelectNodes("//one:T")
Set fNodes = pDoc.DocumentElement.SelectNodes("//one:InsertedFile")
Set iNodes = pDoc.DocumentElement.SelectNodes("//one:Image")
Dim nPageID As String
onote.CreateNewPage newSecId, nPageID, npsDefault
Dim oXML As String
onote.GetPageContent nPageID, oXML, piAll, xs2013
'oXML = pageXML
Dim nDoc As MSXML2.DOMDocument60
Set nDoc = New MSXML2.DOMDocument60
If nDoc.LoadXML(oXML) Then
Dim npNode As MSXML2.IXMLDOMNode
soapNS = "xmlns:one='http://schemas.microsoft.com/office/onenote/2013/onenote'"
nDoc.setProperty "SelectionNamespaces", soapNS
Set npNode = nDoc.SelectSingleNode("//one:Page")
' Find the Title element.
Dim tNode As MSXML2.IXMLDOMNode
Set tNode = nDoc.SelectSingleNode("//one:Page/one:Title/one:OE/one:T")
' Get the CDataSection where OneNote store's the Title's text.
Dim cdataChild As MSXML2.IXMLDOMNode
Set cdataChild = tNode.SelectSingleNode("text()")
' Change the title in the local XML copy.
cdataChild.Text = title
' Write the update to OneNote.
'oneNote.UpdatePageContent doc.XML
'---------- For Text Nodes -----------
For Each cNode In cNodes
If cNode.Text <> "" Then
Dim newTextNodeElement As MSXML2.IXMLDOMElement
Dim newTextNode As MSXML2.IXMLDOMNode
' Create Outline node.
Set newTextNodeElement = nDoc.createElement("one:Outline")
Set newTextNode = npNode.appendChild(newTextNodeElement)
' Create OEChildren.
Set newTextNodeElement = nDoc.createElement("one:OEChildren")
Set newTextNode = newTextNode.appendChild(newTextNodeElement)
' Create OE.
Set newTextNodeElement = nDoc.createElement("one:OE")
Set newTextNode = newTextNode.appendChild(newTextNodeElement)
' Create TE.
Set newTextNodeElement = nDoc.createElement("one:T")
Set newTextNode = newTextNode.appendChild(newTextNodeElement)
' Add the text for the Page's content.
Dim newcd As MSXML2.IXMLDOMCDATASection
Set newcd = nDoc.createCDATASection(cNode.Text)
newTextNode.appendChild newcd
End If
Next
---------- For File Nodes -----------
For Each fNode In fNodes
'Set newFileNode = fNode
Set npNode = npNode.appendChild(fNode)
Next
onote.UpdatePageContent nDoc.XML, DateTime.Now, xs2013
End If
End If
End Sub
The onenote.UpdatePageContent continues to fail with a runtime error -2147213296 (80042010). The code works if i consider only text nodes, while if i add the code for File Nodes it doesn't work anymore.
I tried to change the code for File Nodes with this code:
For Each fNode In fNodes
Dim newFileNodeElement As MSXML2.IXMLDOMElement
Dim newFileNode As MSXML2.IXMLDOMNode
'Set newFileNode = fNode
'Set npNode = npNode.appendChild(fNode)
Set newFileNodeElement = nDoc.createElement("one:InsertedFile")
Set newFileNode = npNode.appendChild(newFileNodeElement)
For i = 0 To fNode.Attributes.Length - 1
Dim attrName As String
Dim attrValue As String
Dim attr As MSXML2.IXMLDOMAttribute
Dim namedNodeMap As MSXML2.IXMLDOMNamedNodeMap
attrName = fNode.Attributes(i).nodeName
attrValue = fNode.Attributes(i).NodeValue
Set attr = nDoc.createNode(2, attrName, "")
attr.Value = attrValue
Set namedNodeMap = nDoc.DocumentElement.LastChild.Attributes
Set newFileNode = namedNodeMap.setNamedItem(attr)
Next i
Next
but the results are the same.
OneNote error codes are listed here, so that error is "The last modified date does not match. ".
The second argument to UpdatePageContent is:
dateExpectedLastModified – (Optional) The date and time that you think
the page you want to update was last modified. If you pass a non-zero
value for this parameter, OneNote proceeds with the update only if the
value you pass matches the actual date and time the page was last
modified. Passing a value for this parameter helps prevent
accidentally overwriting edits users made since the last time the page
was modified.
Remove the DateTime.Now argument (omit it), if you don't need it, or correct the value. It is an additional check parameter, not a date-stamp.
I solved this issue using a different approach: i used Publish method in order to create a new section with the name of the page i needed to copy, and the MergeSections method to copy the page in the section i wished. Here is the code:
Private Sub CreateInNewSection(onote As onenote.Application, pageXML As String,
newSecId As String, title As String, nbID As String, path As String)
Dim pDoc As MSXML2.DOMDocument60
Set pDoc = New MSXML2.DOMDocument60
If pDoc.LoadXML(pageXML) Then
Dim pNode As MSXML2.IXMLDOMNode
soapNS = "xmlns:one='http://schemas.microsoft.com/office/onenote/2013/onenote'"
pDoc.setProperty "SelectionNamespaces", soapNS
Set pNode = pDoc.SelectSingleNode("//one:Page")
Dim oldPageId As String
Dim oldPageName As String
oldPageId = pNode.Attributes.getNamedItem("ID").Text
oldPageName = pNode.Attributes.getNamedItem("name").Text
path = path + "\" + oldPageName + ".one"
Debug.Print path
onote.Publish oldPageId, path, pfOneNote, ""
Dim sXml As String
onote.GetHierarchy nbID, hsSections, sXml, xs2013
Dim sDoc As MSXML2.DOMDocument60
Set sDoc = New MSXML2.DOMDocument60
Dim nowSecId As String
If sDoc.LoadXML(sXml) Then
' select the Section nodes
Dim sNodes As MSXML2.IXMLDOMNodeList
soapNS = "xmlns:one='http://schemas.microsoft.com/office/onenote/2013/onenote'"
sDoc.setProperty "SelectionNamespaces", soapNS
Set sNodes = sDoc.DocumentElement.SelectNodes("//one:Section")
Dim j As Integer
If Not sNodes Is Nothing Then
' Get the first section.
Dim sNode As MSXML2.IXMLDOMNode
For j = 0 To (sNodes.Length - 1)
If sNodes(j).Attributes.getNamedItem("name").Text = oldPageName Then
nowSecId = sNodes(j).Attributes.getNamedItem("ID").Text
Exit For
End If
Next j
End If
onote.MergeSections nowSecId, newSecId
onote.DeleteHierarchy nowSecId
onote.DeleteHierarchy oldPageId
End If
End If
End Sub

Unable to extract value for xml tags

I am a new to vba and have a task to do at hand. I have written some unction but could not debug it properly. I have following string in xml format:
<!--?xml version=""1.0"" encoding=""UTF-8""?-->
<credentials>
<mid>P</mid>
<mpid>Q</mpid>
<accid>R</accid>
<accesskey>S</accesskey>
<secretkey>T</secretkey>
</credentials>
and am trying to extract value corresponding to tag mid, mpid, accid etc.
Here is the subroutine I wrote:
Private Sub ExtractMWSCredentials(Text As String
Set xmldoc = Nothing
DoEvents
Set xmldoc = Get_XML_DOMDocument_Object()
xmldoc.LoadXML (Trim(Text))
Set mId = xmldoc.getElementsByTagName("mid").Item(0).Text
Set mpId = xmldoc.getElementsByTagName("mpid").Item(0).Text
Set accid = xmldoc.getElementsByTagName("accid").Item(0).Text
Set accesskey = xmldoc.getElementsByTagName("accesskey").Item(0).Text
End Sub
Everything is coming out to be empty. Any help appreciated.
I can't even get that code to compile. You don't use the Set keyword to store a non-object value like Text. You could code, for instance,
Set omId = xmldoc.getElementsByTagName("mid").Item(0)
Debug.Print omId.Text
Also, mId is a reserved word so it can't be a variable name. Here's one way you could do it.
Public Sub Extract()
Dim xDoc As MSXML2.DOMDocument60
Dim sMid As String, sMpid As String
Dim sAccid As String, sAccessKey As String
Set xDoc = New MSXML2.DOMDocument60
xDoc.LoadXML "<!--?xml version=""1.0"" encoding=""UTF-8""?-->" & _
"<credentials><mid>P</mid><mpid>Q</mpid><accid>R</accid>" & _
"<accesskey>S</accesskey><secretkey>T</secretkey></credentials>"
sMid = xDoc.getElementsByTagName("mid").Item(0).Text
sMpid = xDoc.getElementsByTagName("mpid").Item(0).Text
sAccid = xDoc.getElementsByTagName("accid").Item(0).Text
sAccessKey = xDoc.getElementsByTagName("accesskey").Item(0).Text
Debug.Print sMid, sMpid, sAccid, sAccessKey
End Sub
I don't use Set because I'm not storing the Item() in a variable, I'm storing the Item().Text in a variable.

Resources