When XML parsing special characters - my specific issue is with ampersands - Excel VBA is throwing the error
Run time error 91: Object variable or With block variable not set
PHP code for XML Parsing:
$xml = '<?xml version="1.0" encoding="UTF-8" ?>
<root> <EngineerFName>'.$engineer_fname.'</EngineerFName>
<CustomerName>'.$customer_name.'</CustomerName>
<EngineerLName>'.$engineer_lname.'</EngineerLName>
<TopName>'.$dt_top_name.'</TopName>
</root>';
Excel Vba code:
Dim XDoc1 As Object
Dim XHTML1 As IHTMLElement
Dim XURL1 As String
Dim CustomerName1 As String
Set XDoc1 = CreateObject("MSXML2.DOMDocument")
XDoc1.async = False: XDoc1.validateOnParse = False
XDoc1.Load ("http://www.beamon.com/windows_application/macro2.php" + "?Id=" + Sheets(1).Range("D2"))
Set lists = XDoc1.DocumentElement
Set getFirstChild = lists.FirstChild
Set getCustomerName1 = lists.ChildNodes(1)
Set getEnglname = lists.ChildNodes(2)
Set getTopCustomer = lists.ChildNodes(3)
Sheets(1).Range("T5") = getCustomerName1.text
Sheets(1).Range("T6") = getFirstChild.text & Space(1) & getEnglname.text
Sheets(1).Range("T7") = getTopCustomer.text
Set XDoc1 = Nothing
Can anyone suggest a solution?
There's a good article on your problem at Techrepublic - quoting the necessary part:
When the XML parser finds an ampersand in the XML data, it expects to find a symbol name and a semicolon following it. The symbol name provides a symbolic reference to another entity or character such as the ampersand, greater-than, and less-than characters. The symbolic name for greater-than is gt and for less-than is lt. To include a greater-than character in the XML data, you must use the following syntax: >
If you already had the response from the PHP page then the solution to your problem is simply to do this:
strXml = VBA.Replace(strXml, "&", "&")
But your issue is that you are using the Load method of the DOMDocument class and the PHP is emitting invalid XML. The PHP page should do this encoding for you - my guess is that it just queries some data store and slots it into an XML string an echos it without doing any validation on the values. Your Load method will not error, but the moment you want to parse the DOM, you get the problem.
Given you are already referencing the MSXML library, perhaps your option is to use the XMLHTTP class to get the response, do the replacement yourself, and then load it the DOMDocument using the LoadXML method. See the code below - it is not tested as I don't know the parameter for your URL:
Option Explicit
Sub TextXMLGetAndParse()
Dim strUrl As String
Dim objXhr As MSXML2.XMLHTTP
Dim strXml As String
Dim objXmlDoc As MSXML2.DOMDocument
Set objXhr = New MSXML2.XMLHTTP
Set objXmlDoc = New MSXML2.DOMDocument
' do a XHR GET to your URL
strUrl = "http://www.beamon.com/windows_application/macro2.php" + "?Id=" + Sheets(1).Range("D2")
With objXhr
.Open "GET", strUrl, False
.send
strXml = .responseXML
End With
' do the clean-up that the PHP page should do for you
strXml = VBA.Replace(strXml, "&", "&")
' load that XML to you DOMDOcument
objXmlDoc.LoadXML strXml
' check values
Debug.Print objXmlDoc.DocumentElement.Text
Debug.Print objXmlDoc.DocumentElement.FirstChild.Text
Debug.Print objXmlDoc.DocumentElement.ChildNodes(1).Text
Debug.Print objXmlDoc.DocumentElement.ChildNodes(2).Text
Debug.Print objXmlDoc.DocumentElement.ChildNodes(3).Text
' clean up
Set objXhr = Nothing
Set objXhr = Nothing
End Sub
Related
I have coded a basic PHP script that collects the data from a few HTML forms and writes it to a text document, and then configured Access to link this text document to a table and import automatically. This all works fine but the problem is that Access seems to be always using this file meaning that my script can't write to it. How can I get Access to check the document every so often, look at what is there and import it without any duplicate data?
two ways:
Dim strFilename As String: strFilename = "C:\temp\yourfile.txt"
Dim strTextLine As String
Dim iFile As Integer: iFile = FreeFile
Open strFilename For Input As #iFile
Do Until EOF(1)
Line Input #1, strTextLine
Loop
Close #iFile
or you can use below code:
Dim intFile As Integer
Dim strFile As String
Dim strIn as String
Dim strOut As String
strOut = vbNullString
intFile = FreeFile()
strFile = "C:\Folder\MyData.txt"
Open strFile For Input As #intFile
Do While Not EOF(intFile)
Line Input #intFile, sIn
If Left(strIn, 7) = "KeyWord" Then
strOut = Mid(strIn, 8)
booFound = True
Exit Do
Loop
Close #intFile
If Len(strOut) > 0 Then
MsgBox "Your Data is " & strOut
Else
MsgBox "Keyword Not Found"
End If
more info . . .More Info . . .
I'm trying to send data from a VB.NET Application to a php application, i found this code:
Private Function SendRequest(uri As Uri, jsonDataBytes As Byte(),contentType As String, method As String) As String
Dim req As WebRequest = WebRequest.Create(uri)
req.ContentType = contentType
req.Method = method
req.ContentLength = jsonDataBytes.Length
Dim stream = req.GetRequestStream()
stream.Write(jsonDataBytes, 0, jsonDataBytes.Length)
stream.Close()
Dim response = req.GetResponse().GetResponseStream()
Dim reader As New StreamReader(response)
Dim res = reader.ReadToEnd()
reader.Close()
response.Close()
Return res
End Function
Dim data = Encoding.UTF8.GetBytes(jsonSring)
Dim result_post = SendRequest(uri, data, "application/json", "POST")
at: source
But I can't get the posted data on php. It sends the headers, but the data no.
So, I need help to figure it out what is missing.
I also had same issue and got solution from comments only. As you are passing binary data then in php you need to read binary data as raw input
To get the Raw Post Data:
<?php $postdata = file_get_contents("php://input"); ?>
Note: I am limited to PHP <-> VBA. Please do not suggest anything that requires an Excel Addon, or any other language/method.
I have a function that connect to a specified URL, submits data, and then retrieves other data. This works great. I'm trying to write it so i can use it as a generic function I can use to connect to any file I need to connect to - each would return different data (one could be user data, one could be complex calculations etc).
When it retrieves the data from PHP, is there a way to dynamically set the variables based on what is received - even if i do not know what has been received.
I can make PHP return to VBA the string in any format, so I'm using the below as an example:
String that is received in vba:
myValue1=Dave&someOtherValue=Hockey&HockeyDate=Yesterday
If i were to parse this in PHP, I could do something similar to (not accurate, just written for example purposes);
$myData = "myValue1=Dave&someOtherValue=Hockey&HockeyDate=Yesterday"
$myArr = explode("&",$myData)
foreach($myArr as $key => $value){
${$key} = $value;
}
echo $someOtherValue; //Would output to the screen 'Hockey';
I would like to do something similar in VBA. The string I am receiving is from a PHP file, so I can format it any way (json etc etc), I just essentially want to be able to define the VARIABLES when outputting the string from PHP. Is this possible in VBA?.
The current state of the function I have that is working great for connections is as below:-
Function kick_connect(url As String, formdata)
'On Error GoTo connectError
Dim http
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "POST", url, False
http.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
http.send (formdata)
kick_connect = http.responseText
Exit Function
connectError:
kick_connect = False
End Function
Ultimately, I want to be able to do something like
sub mySub
myData = "getId=" & Range("A1").Value
myValue = kick_connect("http://path-to-my-php-file.php",myData)
if myValue = False then
'Handle connection error here
exit sub
end if
'do something snazzy here to split "myValue" string (eg "myValue1=Dave&someOtherValue=Hockey&HockeyDate=Yesterday") into own variables
msgbox(myValue1) 'Should output "Dave"
end sub
Obviously I could put the values into an array, and reference that, however I specifically want to know if this exact thing is possible, to allow for flexibility with the scripts that already exist.
I hope this makes sense, and am really grateful for any replies i get.
Thank you.
You can use a Collection:
Dim Tmp As String
Dim s As String
Dim i As Integer
Dim colVariabili As New Collection
Tmp = "myValue1=Dave&someOtherValue=Hockey&HockeyDate=Yesterday"
Dim FieldStr() As String
Dim FieldSplitStr() As String
FieldStr = Split(Tmp, "&")
For Each xx In FieldStr
FieldSplitStr = Split(xx, "=")
colVariabili.Add FieldSplitStr(1), FieldSplitStr(0)
Next
Debug.Print colVariabili("myValue1")
Debug.Print colVariabili("someOtherValue")
Debug.Print colVariabili("HockeyDate")
It's ok if you don't have the correct sequence of var...
I am not sure if this can help you, but as far as I understand your question you want to be able to create the variables dynamically based on the query string parameters. If so then here is example how to add this variables dynamically. Code needs standard module with a name 'QueryStringVariables'. In this module the query string will be parsed and each query string parameter will be added as get-property. If you wish to be able to change the value as well then you will need to add let-property as well.
Add reference to Microsoft Visual Basic For Applications Extensibility
Option Explicit
Private Const SourceQueryString As String = "myValue1=Dave&someOtherValue=Hockey&HockeyDate=Yesterday"
Sub Test()
Dim queryStringVariablesComponent As VBIDE.vbComponent
Dim queryStringVariablesModule As VBIDE.CodeModule
Dim codeText As String
Dim lineNum As Long: lineNum = 1
Dim lineCount As Long
Set queryStringVariablesComponent = ThisWorkbook.VBProject.VBComponents("QueryStringVariables")
Set queryStringVariablesModule = queryStringVariablesComponent.CodeModule
queryStringVariablesModule.DeleteLines 1, queryStringVariablesModule.CountOfLines
Dim parts
parts = Split(SourceQueryString, "&")
Dim part, variableName, variableValue
For Each part In parts
variableName = Split(part, "=")(0)
variableValue = Split(part, "=")(1)
codeText = "Public Property Get " & variableName & "() As String"
queryStringVariablesModule.InsertLines lineNum, codeText
lineNum = lineNum + 1
codeText = variableName & " = """ & variableValue & ""
queryStringVariablesModule.InsertLines lineNum, codeText
lineNum = lineNum + 1
codeText = "End Property"
queryStringVariablesModule.InsertLines lineNum, codeText
lineNum = lineNum + 1
Next
DisplayIt
End Sub
Sub DisplayIt()
MsgBox myValue1 'Should output "Dave"
End Sub
I need a PHP Server to interact with my wireless sensors. But I also need that server to be controlled by a Visual Basic Application.
Some of the features I need in the Visual Basic Application:
Start/Stop Server
Server Configuration
Access files on the server directory.
The PHP file (server application) is simply to accept data from the wireless sensor module and store in a flat database file (CSV, XML). After this data has been written Visual Basic must access the flat database file to perform analysis.
Any suggestions on what server to use and what particular methods might provide the easiest solution?
Well, what you want is broad and yet, there is not enough information about your PHP part.
But I can help you with VB.NET. Here is a Class (And a Sub and an Event) that can really help.
Some Examples first
Simply loads a HTML code:
Dim Page As New WEBhtml("http://www.example.com/index.php?get=something")
While Page.IsReady = False
End While
If IsNothing(Page.Exception) Then
MsgBox(Page.GetHtml)
Else
MsgBox(Page.Exception.Message)
End If
Sends POST to destination (just the Dim line):
Dim Page As New WEBhtml("http://www.example.com/index.php?get=something", {"a=alpha", "b=beta", "c=I Don't Know :D !"})
Use Handling:
Private Sub form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim Page As New WEBhtml("http://www.e.com/i.php")
End Sub
Private Sub html_done(ByRef sender As WEBhtml) Handles Me.WebHtml_Done
MsgBox("Timetook: " & sender.TimeTook / 1000 & "s")
MsgBox("Url: " & sender.Url)
If IsNothing(sender.Exception) Then
MsgBox("Bandwidth: " & sender.Bytes / 1024 & "kb")
MsgBox("HTML: " & sender.GetHtml)
Else
MsgBox("Error: " & sender.Exception.Message)
End If
End Sub
See? very easy.
Now to Start
Follow these two steps
First: Add System.Web Reference
Go to Project > [Project name] Properties > Reference
After that press the "Add..." button, and check 'System.Web'
Then press Ok. also check it in 'Imported namespaces'
Second: Copy this block before the 'End Class'
Public Shared Event WebHtml_Done(ByRef sender As WEBhtml)
Friend Shared Sub RaiseDone(ByRef wh As WEBhtml)
RaiseEvent WebHtml_Done(wh)
End Sub
Public Class WEBhtml
Private thrd As Threading.Thread
Private Err As Exception
Private BytesUsed As ULong = 0
Private Time As UInteger = 0
Private Html As String = ""
Private _Url As String
Private tmr As New Timer
Private Sub initialize()
tmr.Interval = 50
AddHandler tmr.Tick, AddressOf Tick
tmr.Enabled = True
tmr.Start()
End Sub
Public Sub New(ByVal Url As String)
thrd = New Threading.Thread(Sub() WEB_POST(Url))
initialize()
thrd.Start()
End Sub
Public Sub New(ByVal Url As String, ByVal PostData As String())
thrd = New Threading.Thread(Sub() WEB_POST(Url, PostData))
initialize()
thrd.Start()
End Sub
Private Sub Tick(sender As Object, e As EventArgs)
If thrd.IsAlive = False Then
tmr.Enabled = False
RaiseDone(Me)
End If
End Sub
Private Sub WEB_POST(ByVal url As String, Optional ByVal values() As String = Nothing)
_Url = url
Dim data As String = ""
Dim a, b As Integer
b = My.Computer.Clock.TickCount
Try
For i = 0 To values.GetLength(0) - 1
a = values(i).IndexOf("=")
If a >= 0 Then
data += System.Web.HttpUtility.UrlEncode(Mid(values(i), 1, a)) & "=" & System.Web.HttpUtility.UrlEncode(Mid(values(i), a + 2))
If i < values.GetLength(0) - 1 Then data += "&"
End If
Next
Catch
data = ""
End Try
Try
Dim request As System.Net.WebRequest = System.Net.WebRequest.Create(url)
request.Method = "POST"
Dim postdata As String = data
Dim byteArray As Byte() = System.Text.Encoding.UTF8.GetBytes(postdata)
request.ContentType = "application/x-www-form-urlencoded"
request.ContentLength = byteArray.Length
request.Timeout = 100000
Dim dataStream As IO.Stream = request.GetRequestStream()
dataStream.Write(byteArray, 0, byteArray.Length)
dataStream.Close()
Dim response As Net.WebResponse = request.GetResponse()
dataStream = response.GetResponseStream()
Dim reader As New IO.StreamReader(dataStream)
Dim responseFromServer As String = reader.ReadToEnd()
reader.Close()
dataStream.Close()
response.Close()
BytesUsed += responseFromServer.Length + byteArray.Length
Time = My.Computer.Clock.TickCount - b
Html = (responseFromServer)
Catch ex As Exception
Err = ex
Time = My.Computer.Clock.TickCount - b
Html = ""
End Try
End Sub
Public ReadOnly Property Exception() As Exception
Get
Return Err
End Get
End Property
Public ReadOnly Property TimeTook() As UInteger
Get
Return Time
End Get
End Property
Public ReadOnly Property Bytes() As ULong
Get
Return BytesUsed
End Get
End Property
Public ReadOnly Property GetHtml() As String
Get
Return Html
End Get
End Property
Public ReadOnly Property IsReady() As Boolean
Get
Return Not thrd.IsAlive
End Get
End Property
Public ReadOnly Property Url() As String
Get
Return _Url
End Get
End Property
End Class
I believe this works properly.
Hope It Helps.
Hoi!
I trying to make a webservice in Windows.
The client is Delphi 6, with MSXML2.XMLHTTP call, and other side is PHP.
First I tested: can I receive hungarian XML?
The PHP source was UTF-8 encoded file (PSPAD).
$s = 'alma árvíztűrő tükörfúrógép beta';
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;
$m = $doc->createElement('package');
$doc->appendChild($m);
$n = $doc->createElement('Msg');
$m->appendChild($n);
$n->nodeValue = $s;
$xs = $doc->saveXML();
header('Content-Type: text/xml');
echo($xs);
This package I fully got in Delphi side, the accents are ok.
So then I tried to inject data from xml (post xml to php with accents).
global $HTTP_RAW_POST_DATA;
$xmlstr = $HTTP_RAW_POST_DATA;
$xml = new SimpleXMLElement($xmlstr);
$msg = $xml->msg;
#$msg = 'ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP';
Ok, I got the "msg" field, but nevertheless I trying to convert it, everytime I got some convert notice, and the result is not same as when I set the variable directly...
The debug is:
echo(utfCharToNumber($sinput).'<br>');
echo(utfCharToNumber($sdefined).'<br>');
Input: 195[ ]8286195141908419717682197[ ]328419
Defined: 195[129]8286195141908419717682197[144]328419
Input: 5156751951508270195154821951477119513780<br>
Defined: 5156751951508270195154821951477119513780<br>
As you see that two holes I have in the variable when I converted the input from MSXML2.
I really don't understand this.
I cannot reproduce same XML output from get the data from input XML as when I set directly in PHP code...
Why?
Thanks for your every help, idea, link, document, little example!
dd
Since you haven't included Delphi source, I suspect you're posting data straight from a string as content body of the request, which is encoded in the current ANSI encoding by default in Delphi 6. I advise you either use Utf8Encode on the string before you add this as the request's body data, or add a 'Content-encoding' request header with the name of the ANSI encoding (if I remember correctly GetLocaleInfo could give you this).
The source of the problem was the Delphi code.
Priorly I used AnsiToUTF8 to encode the XML text.
But the COM object is uses UTF16 as I think.
The working code is this:
procedure TForm1.Button4Click(Sender: TObject);
var
mhttp : variant;
ws : WideString;
tosend : OleVariant;
xml : TXMLDocument;
n : IXMLNode;
begin
mhttp := CreateOleObject('MSXML2.XMLHTTP');
mhttp.Open('POST', 'http://127.0.0.1/test_xmlgen.php', False);
xml := CreateANewDocument(Self, '', 'a');
n := xml.DocumentElement.AddChild('msg');
n.NodeValue := 'ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP';
xml.SaveToXML(ws);
tosend := ws;
mhttp.send(tosend);
Memo1.Lines.Text :=
IntToStr(mhttp.Status) + #13 +
mhttp.responseText + #13;
end;
This can resend the XML I sent - with good accents.
Thanks for your help:
dd