How to query HTML with x XPath expression in C++? - c++

I have a webbrowser and I use DocumentComplete to read the current document from the WebBrowser (as IHTMLDocument2).
What's the easiest way to run xpath queries in that html doc? I am looking for something easy to use and lightweight.
I am using Visual Studio C++ 2010.

What's the easiest way to run xpath
queries in that html doc? I am looking
for something easy to use and
lightweight.
I am using Visual Studio C++ 2010.
Generally, XPath expressions cannot be evaluated against HTML documents.
However, if the HTML document is also an XHTML document (which is by definition a well-formed XML document), then XPath expressions can be evaluated against it.
In particular using MS Visual C++, one can use code like this:
#include <stdio.h>
#import <msxml3.dll>
using namespace MSXML2;
void dump_com_error(_com_error &e);
int main(int argc, char* argv[])
{
CoInitialize(NULL);
try{
IXMLDOMDocument2Ptr pXMLDoc = NULL;
HRESULT hr = pXMLDoc.CreateInstance(__uuidof(DOMDocument30));
// Set parser property settings
pXMLDoc->async = VARIANT_FALSE;
// Load the sample XML file
hr = pXMLDoc->load("hello.xsl");
// If document does not load report the parse error
if(hr!=VARIANT_TRUE)
{
IXMLDOMParseErrorPtr pError;
pError = pXMLDoc->parseError;
_bstr_t parseError =_bstr_t("At line ")+ _bstr_t(pError->Getline())
+ _bstr_t("\n")+ _bstr_t(pError->Getreason());
MessageBox(NULL,parseError, "Parse Error",MB_OK);
return 0;
}
// Otherwise, build node list using SelectNodes
// and returns its length as console output
else
pXMLDoc->setProperty("SelectionLanguage", "XPath");
// Set the selection namespace URI if the nodes
// you wish to select later use a namespace prefix
pXMLDoc->setProperty("SelectionNamespaces",
"xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
IXMLDOMElementPtr pXMLDocElement = NULL;
pXMLDocElement = pXMLDoc->documentElement;
IXMLDOMNodeListPtr pXMLDomNodeList = NULL;
pXMLDomNodeList = pXMLDocElement->selectNodes("//xsl:template");
int count = 0;
count = pXMLDomNodeList->length;
printf("The number of <xsl:template> nodes is %i.\n", count);
}
catch(_com_error &e)
{
dump_com_error(e);
}
return 0;
}
void dump_com_error(_com_error &e)
{
printf("Error\n");
printf("\a\tCode = %08lx\n", e.Error());
printf("\a\tCode meaning = %s", e.ErrorMessage());
_bstr_t bstrSource(e.Source());
_bstr_t bstrDescription(e.Description());
printf("\a\tSource = %s\n", (LPCSTR) bstrSource);
printf("\a\tDescription = %s\n", (LPCSTR) bstrDescription);
}
Read more about this code example here.

Related

How to use Xerces to parse XML in a string [duplicate]

I know how to create a complete dom from an xml file just using XercesDOMParser:
xercesc::XercesDOMParser parser = new xercesc::XercesDOMParser();
parser->parse(path_to_my_file);
parser->getDocument(); // From here on I can access all nodes and do whatever i want
Well, that works... but what if I'd want to parse a string? Something like
std::string myxml = "<root>...</root>";
xercesc::XercesDOMParser parser = new xercesc::XercesDOMParser();
parser->parse(myxml);
parser->getDocument(); // From here on I can access all nodes and do whatever i want
I'm using version 3. Looking inside the AbstractDOMParser I see that parse method and its overloaded versions, only parse files.
How can I parse from a string?
Create a MemBufInputSource and parse that:
xercesc::MemBufInputSource myxml_buf(myxml.c_str(), myxml.size(),
"myxml (in memory)");
parser->parse(myxml_buf);
Use the following overload of XercesDOMParser::parse():
void XercesDOMParser::parse(const InputSource& source);
passing it a MemBufInputSource:
MemBufInputSource src((const XMLByte*)myxml.c_str(), myxml.length(), "dummy", false);
parser->parse(src);
Im doing it another way. If this is incorrect, please tell me why. It seems to work.
This is what parse expects:
DOMDocument* DOMLSParser::parse(const DOMLSInput * source )
So you need to put in a DOMLSInput instead of a an InputSource:
xercesc::DOMImplementation * impl = xercesc::DOMImplementation::getImplementation();
xercesc::DOMLSParser *parser = (xercesc::DOMImplementationLS*)impl)->createLSParser(xercesc::DOMImplementation::MODE_SYNCHRONOUS, 0);
xercesc::DOMDocument *doc;
xercesc::Wrapper4InputSource source (new xercesc::MemBufInputSource((const XMLByte *) (myxml.c_str()), myxml.size(), "A name");
parser->parse(&source);
You may use MemBufInputSource as found in the xercesc/framework/MemBufInputSource.cpp, and the header file, MemBufInputSource.hpp contains extensive documentation, as similar to answers above:
#include <xercesc/framework/MemBufInputSource.hpp>
char* myXMLBufString = "<root>hello xml</root>";
MemBufInputSource xmlBuf((const XMLByte*)myXMLBufString, 23, "myXMLBufName", false);
But take note, this doesn't seem to work unless you first initialize the system, as below (taken from the xerces-c-3.2.3/samples/src/SAX2Count/SAX2Count.cpp)
bool recognizeNEL = false;
char localeStr[64];
memset(localeStr, 0, sizeof localeStr);
// Initialize the XML4C2 system
try {
if (strlen(localeStr)) {
XMLPlatformUtils::Initialize(localeStr);
} else {
XMLPlatformUtils::Initialize();
}
if (recognizeNEL) {
XMLPlatformUtils::recognizeNEL(recognizeNEL);
}
} catch (const XMLException& toCatch) {
XERCES_STD_QUALIFIER cerr << "Error during initialization! Message:\n"
<< StrX(toCatch.getMessage()) << XERCES_STD_QUALIFIER endl;
return 1;
}
Of course reading a file wouldn't require thinking about this type of prep since you just pass a file path to the program which the parser takes. So for those experiencing seg faults, this could be the answer.

XSL Transformation in C++ catch message with terminate flag

I've made a project basing on this document to retrieve information from XML file basing on XSL file.
I am trying to throw an error in XSL file:
<xsl:if test="not(PIN/Length/text() = '4')">
<xsl:message terminate="yes">PIN length in input suppose to be 4</xsl:message>
</xsl:if>
But it seems not to work (no errors during work) - just like it is successfully done.
Can I somehow catch this message in C++?
void ManageXML::XML2Generic(string sOrgFilePath, string sOrgXSLFilePath, string sCpfPath)
{
wstring sTempFilePath = s2ws(sOrgFilePath);
LPCWSTR sFilePath = sTempFilePath.c_str();
wstring sTempXSLFilePath = s2ws(sOrgXSLFilePath);
LPCWSTR sXSLFilePath = sTempXSLFilePath.c_str();
HRESULT hr = S_OK;
IXMLDOMDocument *pXMLDom = nullptr;
IXMLDOMDocument *pXSLDoc = nullptr;
CHK_HR(CreateAndInitParserDOM(&pXMLDom));
CHK_HR(LoadXMLFile(pXMLDom, sFilePath, sOrgFilePath)); //cast to LPCWSTR
CHK_HR(CreateAndInitParserDOM(&pXSLDoc));
CHK_HR(LoadXMLFile(pXSLDoc, sXSLFilePath, sOrgXSLFilePath)); //cast to LPCWSTR
// Transform dom to a string:
CHK_HR(TransformDOM2Data(pXMLDom, pXSLDoc, sGenericResult));
CleanUp:
SAFE_RELEASE(pXSLDoc);
SAFE_RELEASE(pXMLDom);
this->CreateGenericFile(sCpfPath);
CoUninitialize();
}
One bad solution that comes to my mind is to make XSL like this:
<xsl:if test="not(PIN/Length/text() = '4')">
<xsl:text>MSXML_ERROR: PIN length in input suppose to be 4</xsl:message>
</xsl:if>
And
CleanUp:
SAFE_RELEASE(pXSLDoc);
SAFE_RELEASE(pXMLDom);
if (sGenericResult.find("MSXML_ERROR") != string::npos)
throw runtime_error("blah blah blah");
this->CreateGenericFile(sCpfPath);
CoUninitialize();

Unable to set Reporting Services Parameters

I'm generating a reporting services report from an ASP.NET (MVC) based application but am having problems setting the parameters for the report.
I believe the issue has only occurred since we upgraded SQL Server from 2005 to 2008 R2 (and Reporting Services along with it).
The original error encountered was from calling rsExec.Render:
Procedure or function 'pCommunication_ReturnRegistrationLetterDetails'
expects parameter '#guid', which was not supplied.
Debugging the code I noticed that rsExec.SetExecutionParameters is returning the following response:
Cannot call 'NameOfApp.SQLRSExec.ReportExecutionService.SetExecutionParameters(NameOfApp.SQLRSExec.ParameterValue[],
string)' because it is a web method.
Here is the function in it's entirety:
public static bool ProduceReportToFile(string reportname, string filename, string[,] reportparams,
string fileformat)
{
bool successful = false;
SQLRS.ReportingService2005 rs = new SQLRS.ReportingService2005();
SQLRSExec.ReportExecutionService rsExec = new NameOfApp.SQLRSExec.ReportExecutionService();
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
rsExec.Credentials = System.Net.CredentialCache.DefaultCredentials;
// Prepare Render arguments
string historyID = null;
string deviceInfo = null;
// Prepare format - available options are "PDF","Word","CSV","TIFF","XML","EXCEL"
string format = fileformat;
Byte[] results;
string encoding = String.Empty;
string mimeType = String.Empty;
string extension = String.Empty;
SQLRSExec.Warning[] warnings = null;
string[] streamIDs = null;
// Define variables needed for GetParameters() method
// Get the report name
string _reportName = reportname;
string _historyID = null;
bool _forRendering = false;
SQLRS.ParameterValue[] _values = null;
SQLRS.DataSourceCredentials[] _credentials = null;
SQLRS.ReportParameter[] _parameters = null;
// Get if any parameters needed.
_parameters = rs.GetReportParameters(_reportName, _historyID,
_forRendering, _values, _credentials);
// Load the selected report.
SQLRSExec.ExecutionInfo ei =
rsExec.LoadReport(_reportName, historyID);
// Prepare report parameter.
// Set the parameters for the report needed.
SQLRSExec.ParameterValue[] parameters =
new SQLRSExec.ParameterValue[1];
// Place to include the parameter.
if (_parameters.Length > 0)
{
for (int i = 0; i < _parameters.Length; i++)
{
parameters[i] = new SQLRSExec.ParameterValue();
parameters[i].Label = reportparams[i,0];
parameters[i].Name = reportparams[i, 0];
parameters[i].Value = reportparams[i, 1];
}
}
rsExec.SetExecutionParameters(parameters, "en-us");
results = rsExec.Render(format, deviceInfo,
out extension, out encoding,
out mimeType, out warnings, out streamIDs);
// Create a file stream and write the report to it
using (FileStream stream = System.IO.File.OpenWrite(filename))
{
stream.Write(results, 0, results.Length);
}
successful = true;
return successful;
}
Any ideas why I'm now unable to set parameters? The report generation works without issue if parameters aren't required.
Looks like it may have been an issue with how reporting services passes parameters through to the stored procedure providing the data. A string guid was being passed through to the report and the stored procedure expected a varchar guid. I suspect reporting services may have been noticing the string followed the guid format pattern and so passed it through as a uniqueidentifier to the stored procedure.
I changed the data source for the report from "stored procedure" to "text" and set the SQL as "EXEC pMyStoredOProcName #guid".
Please note the guid being passed in as a string to the stored procedure is probably not best practice... I was simply debugging an issue with another developers code.
Parameter _reportName cannot be null or empty. The [CLASSNAME].[METHODNAME]() reflection API could not create and return the SrsReportNameAttribute object
In this specific case it looks like an earlier full compile did not finish.
If you encounter this problem I would suggest that you first compile the class mentioned in the error message and see if this solves the problem.
go to AOT (get Ctrl+D)
in classes find CLASSNAME
3.compile it (F7)

Making Xerces parse a string instead of a file

I know how to create a complete dom from an xml file just using XercesDOMParser:
xercesc::XercesDOMParser parser = new xercesc::XercesDOMParser();
parser->parse(path_to_my_file);
parser->getDocument(); // From here on I can access all nodes and do whatever i want
Well, that works... but what if I'd want to parse a string? Something like
std::string myxml = "<root>...</root>";
xercesc::XercesDOMParser parser = new xercesc::XercesDOMParser();
parser->parse(myxml);
parser->getDocument(); // From here on I can access all nodes and do whatever i want
I'm using version 3. Looking inside the AbstractDOMParser I see that parse method and its overloaded versions, only parse files.
How can I parse from a string?
Create a MemBufInputSource and parse that:
xercesc::MemBufInputSource myxml_buf(myxml.c_str(), myxml.size(),
"myxml (in memory)");
parser->parse(myxml_buf);
Use the following overload of XercesDOMParser::parse():
void XercesDOMParser::parse(const InputSource& source);
passing it a MemBufInputSource:
MemBufInputSource src((const XMLByte*)myxml.c_str(), myxml.length(), "dummy", false);
parser->parse(src);
Im doing it another way. If this is incorrect, please tell me why. It seems to work.
This is what parse expects:
DOMDocument* DOMLSParser::parse(const DOMLSInput * source )
So you need to put in a DOMLSInput instead of a an InputSource:
xercesc::DOMImplementation * impl = xercesc::DOMImplementation::getImplementation();
xercesc::DOMLSParser *parser = (xercesc::DOMImplementationLS*)impl)->createLSParser(xercesc::DOMImplementation::MODE_SYNCHRONOUS, 0);
xercesc::DOMDocument *doc;
xercesc::Wrapper4InputSource source (new xercesc::MemBufInputSource((const XMLByte *) (myxml.c_str()), myxml.size(), "A name");
parser->parse(&source);
You may use MemBufInputSource as found in the xercesc/framework/MemBufInputSource.cpp, and the header file, MemBufInputSource.hpp contains extensive documentation, as similar to answers above:
#include <xercesc/framework/MemBufInputSource.hpp>
char* myXMLBufString = "<root>hello xml</root>";
MemBufInputSource xmlBuf((const XMLByte*)myXMLBufString, 23, "myXMLBufName", false);
But take note, this doesn't seem to work unless you first initialize the system, as below (taken from the xerces-c-3.2.3/samples/src/SAX2Count/SAX2Count.cpp)
bool recognizeNEL = false;
char localeStr[64];
memset(localeStr, 0, sizeof localeStr);
// Initialize the XML4C2 system
try {
if (strlen(localeStr)) {
XMLPlatformUtils::Initialize(localeStr);
} else {
XMLPlatformUtils::Initialize();
}
if (recognizeNEL) {
XMLPlatformUtils::recognizeNEL(recognizeNEL);
}
} catch (const XMLException& toCatch) {
XERCES_STD_QUALIFIER cerr << "Error during initialization! Message:\n"
<< StrX(toCatch.getMessage()) << XERCES_STD_QUALIFIER endl;
return 1;
}
Of course reading a file wouldn't require thinking about this type of prep since you just pass a file path to the program which the parser takes. So for those experiencing seg faults, this could be the answer.

making a web services query using gSoap with query arguments

I'm attempting to convert a soap query written for C# into a gSoap query in Visual C++.
The C# query adds an XML node's to the query call, in order to pass parameters to the query:
XmlNode queryOpts = xmlDoc.CreateNode(XmlNodeType.Element, "QueryOptions", "");
queryOpts.InnerXml = "<DateInUtc>TRUE</DateInUtc>";
Here's the C# query, passing various args (some args are specified as XmlNode objects)
XmlNode nodeListItems = listService.GetListItems("Announcements", null, query, viewFields, null, queryOpts, null);
The C++ / gSoap query allows me to pass a query and response object:
listService.__ns10__GetListItems(&announcementQuery, &announcementResponse)
The query object has various properties that can be set that relate to the arguments in the C# call:
announcementQuery.listName
announcementQuery.query
announcementQuery.queryOptions
announcementQuery.viewFields
The first argument there is a string, no problem.
The query, queryOptions and viewFields are a bit confusing.
"query" is a class of type _ns2__GetListItems_query, and it has the following functions & members:
soap_default()
soap_get()
soap_in()
soap_out()
soap_put()
soap_serialize()
soap_type()
__any
__mixed
for query, queryOptions and viewFields, I'd simply like to specify an xml formatted string, like the C# code does, but I'm not sure how this is done.
Can someone cast some experience on this?
thanks!
I'm assuming you've already discovered the answer to this, but I'll post some notes for posterity.
Here's a simple C++ demo for sending and XML doc to a ASP.NET web method.
int _tmain(int argc, _TCHAR* argv[])
{
Service1SoapProxy proxy;
_WebServiceNS1__HelloWorld helloWorld;
_WebServiceNS1__HelloWorld_xml xml;
_WebServiceNS1__HelloWorldResponse response;
xml.__any = "<doc><x>hi</x></doc>";
helloWorld.xml = &xml;
int result = proxy.HelloWorld(&helloWorld, &response);
fprintf(stdout, "result: %i\n", result);
switch(result)
{
case SOAP_OK:
fprintf(stdout, "Response: %s\n", response.HelloWorldResult);
break;
default:
break;
}
return 0;
}
Here's the trivial web method in the .NET service:
[WebMethod]
public string HelloWorld(XmlNode xml)
{
return string.Format("Your XML: {0}", xml.OuterXml);
}
If everything works, you'll see "Response: hi" on your console.