How do I retrieve embedded XML values using MSXML in AutoItV3? - autoit

I am trying to use AutoItV3 to automate the insertion of some Entities into a piece of software.
It will be far easier if my automation can read information from an xml file and use this to generate my entities, as I can then parse in different xml files for different tests.
I am using a popular extension MSXML to try and do this. This can be found here:
https://www.autoitscript.com/forum/applications/core/interface/file/attachment.php?id=44418
My XML is a relatively simply structure where I will have various fields under each 'Entity' within all of my 'Entities'
<?xml version="1.0" encoding="UTF-8"?>
<entities>
<entity>
<name>
Mation Jr, Mr Auto
</name>
<legalname>
Mr Auto Mation Jr
</legalname>
</entity>
<entity>
<name>
Mation Sr, Mr Auto
</name>
<legalname>
Mr Auto Mation Sr
</legalname>
</entity>
</entities>
In my script header I am importing the MSXML au3 file and setting the XML path
#include <_MSXML.au3>
; Set the XML file
$xmlpath = #ScriptDir & "\Entity.xml"
My Question is, how can I iterate through the attributes of each Entity within all Entities?
This is what I have so far, but i am not understanding how I would retrieve values from an individual entity listed under the Entities node:
; Fetch All Entities from XAML
$ENTITIES = _MSXML_SelectNodes($oXml, "entities/entity")
If ($ENTITIES[0] > 0) Then
; This part works and will iterate for x amount of entities provided
; Fetch Entity as pos $i
For $i = 1 To $ENTITIES[0] Step 1
; How can I iterate through attributes from ENTITIES[$i] ??
Next
Else
MsgBox(4096, 'Error', 'No entity was provided')
EndIf
I understand my question is quite broad but I think there should be enough information to start with

This issue with that UDF is that it seems to want to return strings for everything instead of xml objects which are more useful. I would avoid it and instead just just use the com object yourself with $oXml = ObjCreate("Msxml2.DOMDocument") and then have a look at the documentation here.
But anyways, I think this code will get you what you want:
; Set the XML file
$xmlpath = #ScriptDir & "\Entity.xml"
$oXml = ObjCreate("Msxml2.DOMDocument")
$oXml.load($xmlpath)
; Fetch All Entities from XAML
$objNodeList = $oXml.selectNodes("entities/entity")
For $node in $objNodeList
ConsoleWrite($node.nodename & #CRLF)
$objChildNodeList = $node.selectNodes("*")
For $ChildNode in $objChildNodeList
ConsoleWrite(#TAB & $ChildNode.nodename & ' = ' & $ChildNode.text & #CRLF)
Next
Next
Notice how there is really no need to use a UDF and you can just use the com object's built in methods. In my opinion, this is simpler than using the UDF.
Another thing in general that is worth mentioning is that if you ever have trouble figuring out how to do anything in autoit you can try searching for how to do that same thing in vba or vbs since the languages are pretty similar and autoit can use all the com objects that are used in vba/vbs. When vba/vbs does something like this Set oXml = CreateObject("Msxml2.DOMDocument") just do this in autoit: $oXml = ObjCreate("Msxml2.DOMDocument").

Related

How to add a value to the existing element value and return it as a new value

This is the xml file.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<AtcoCode> System-Start-Date= 2018-05-16T12:35:48.6929328-04:00, " ", System-End-Date = 9999-12-31, " ", 150042010003</AtcoCode>
<NaptanCode>esxatgjd</NaptanCode>
<PlateCode>
</PlateCode>
<CleardownCode>
</CleardownCode>
<CommonName>Upper Park</CommonName>
<CommonNameLang>
</CommonNameLang>
<ShortCommonName>
</ShortCommonName>
<ShortCommonNameLang>
</ShortCommonNameLang>
<Landmark>Upper Park</Landmark>
<LandmarkLang>
</LandmarkLang>
<Street>High Road</Street>
<StreetLang>
</StreetLang>
<Crossing>
</Crossing>
<CrossingLang>
</CrossingLang>
<Indicator>adj</Indicator>
<IndicatorLang>
</IndicatorLang>
<Bearing>NE</Bearing>
<NptgLocalityCode>E0046286</NptgLocalityCode>
<LocalityName>Loughton</LocalityName>
<ParentLocalityName>
</ParentLocalityName>
<GrandParentLocalityName>
</GrandParentLocalityName>
<Town>Loughton</Town>
<TownLang>
</TownLang>
<Suburb>
</Suburb>
<SuburbLang>
</SuburbLang>
<LocalityCentre>1</LocalityCentre>
<GridType>U</GridType>
<Easting>541906</Easting>
<Northing>195737</Northing>
<Co-ordinates>51.64255,0.04944</Co-ordinates>
<StopType>BCT</StopType>
<BusStopType>MKD</BusStopType>
<TimingStatus>OTH</TimingStatus>
<DefaultWaitTime>
</DefaultWaitTime>
<Notes>
</Notes>
<NotesLang>
</NotesLang>
<AdministrativeAreaCode>080</AdministrativeAreaCode>
<CreationDateTime>2006-11-06T00:00:00</CreationDateTime>
<ModificationDateTime>2010-01-16T07:58:02</ModificationDateTime>
<RevisionNumber>5</RevisionNumber>
<Modification>rev</Modification>
<Status>act</Status>
</root>
How to achieve this?
Question: Create the path range index for the status element and fetch all the documents that has status del
after fetching all the documents, you need to create the new element called currentreservationnumber under RevisionNumber element.
The value of the currentrevisionnumber will be +1 to the RevisionNumber.
I think the warning about sequential numbers is related to system-wide unique numbers/ids (like Oracle sequence), so not a worry in this case?
If you only ever have one RevisionNumber, and you can find it without a path index, you can maybe get by with element-value query on the RevisionNumber since it's already indexed.
Given that you get the document somehow, it could be as simple as:
let $doc := fn:doc ('/foo.xml')
let $rev-node := $doc/root/RevisionNumber
return xdmp:node-insert-after ($rev-node, <currentreservationnumber>{$rev-node + 1}</currentreservationnumber>)
though remember to consider locking if you are doing a big query/update. And you might need to switch to node-replace if there is already a currentreservationnumber.

XmlReader how to read or skip a specific child that does not always exist

I have a big XML file that I must read with XmlReader because it can not be loaded into memory. This XML is formatted in this way (is a reduced version):
<?xml version="1.0" encoding="windows-1252"?>
<Products>
<Product>
<Code>A14</Code>
<Name>Name1</Name>
<Manufacturer>
<Name>ManufacturerName</Name>
</Manufacturer>
<ProdCategories>
<ProdCategory>
<Code>015</Code>
<Name>ProdCategoryName</Name>
</ProdCategory>
</ProdCategories>
<Barcodes> <!-- note this line -->
</Barcodes>
</Product>
<Product>
<Code>A15</Code>
<Name>Name2</Name>
<Manufacturer>
<Name>ManufacturerName</Name>
</Manufacturer>
<ProdCategories>
<ProdCategory>
<Code>016</Code>
<Name>ProdCategoryName</Name>
</ProdCategory>
</ProdCategories>
<Barcodes>
<Barcode>
<Code>1234567890</Code> <!-- note this line -->
</Brcode>
</Barcodes>
</Product>
Note the <Barcode> <Code> elements: in the first <product> is missing.
This is the code that I use for read it and for put these data in a database:
XmlReader reader = XmlReader.Create("Products.xml");
reader.MoveToContent();
do
{
reader.ReadToFollowing("Code");
code = reader.ReadElementContentAsString();
reader.ReadToFollowing("Name");
Name = reader.ReadElementContentAsString();
reader.ReadToFollowing("Name");
ManufacturerName = reader.ReadElementContentAsString();
reader.ReadToFollowing("Code");
ProdCategoryCode = reader.ReadElementContentAsString();
reader.ReadToFollowing("Code");
BarcodeCode = reader.ReadElementContentAsString();
//Here I use "code", "Name", "ManufacturerName" variables to insert into a database
} while (reader.Read());
reader.Close();
All XML tags are present in all products except the <Barcodes> childs (<Barcode><Code>) that is present only on some product, then I cannot jump at next "code" with last ReadToFollowing because if not present I capture the first <product><code>.
I cant control XML output and cant modify it (is third-party).
There's a way to "ReadToFollowing('<Barcodes><Barcode><Code>')" so that I can specific what should seek and if there is not found I can jump it?
Thank you for your help, excuse my bad english.
I would suggest to pull each Product element into a tree model, using either https://msdn.microsoft.com/en-us/library/system.xml.linq.xnode.readfrom(v=vs.110).aspx or https://msdn.microsoft.com/en-us/library/system.xml.xmldocument.readnode(v=vs.110).aspx, then you can use LINQ to XML query methods or XPath to read out the data of each Product in a safe way while maintaining a low memory footprint.

Using XQUERY to retrieve attributes value

Is it possible to use XQUERY to retrieve the attributes filename from the following XML? I am trying to use /preFileDoc/inpXML/#filename but it doesn't work...
<?xml version="1.0"?>
<preFileDoc xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<senderId>ABC</senderId>
<receiverId>XYZ</receiverId>
<tranxCode>A001</tranxCode>
<inpXML version="1.0" encoding="UTF-8">
<soap-env:Envelope>
<soap-env:Header msgcode="SPPCONVAKT" orig-system="002FTB" refid="65355ff50a172064484bf9da64c1e245" timestamp="2009-02-11 21:00:10.741" filename="SPPCONVAKT20090128001.dat"/>
<soap-env:Body>
text1
text2
</soap-env:Body>
</soap-env:Envelope>
</inpXML>
</preFileDoc>
ps: Sometimes the filename attributes is sent as fileName in the incoming XML..thinking to retrieve value from attributes #filename OR #fileName.. can it achieve in single XQUERY? Thanks for advice...
I think your XPath is incomplete. The last child-step / in /preFileDoc/inpXML/#filename only matches attributes of the inpXML element, not its descendants.
One way to solve the problem would be the //-step:
/preFileDoc/inpXML//#filename
Note that this would find all attributes named filename in the soapenv:Body, too.
A more robust way would thus be to declare the soapenv prefix in the XQuery:
declare namespace soap-env="http://schemas.xmlsoap.org/soap/envelope/";
return /preFileDoc/inpXML//soap-env:Header/#filename
Finally, the different capitalizations of filename can be worked around by specifying both:
declare namespace soap-env="http://schemas.xmlsoap.org/soap/envelope/";
return /preFileDoc/inpXML//soap-env:Header/(#filename | #fileName)
You can take the union of multiple attributes. It will be unlikely that this attribute will appear multiple times with different casing, so that should always return a single node:
//soap-env:Header/#filename | //soap-env:Header/#fileName
Optionally, you could wrap it in parentheses, and add [1] behind it, to always take the first result.
(//soap-env:Header/#filename | //soap-env:Header/#fileName)[1]
If you replace the union with a comma, which creates a sequence instead of a document order node set, you can add a default as well at the end. Maybe not very usefull here, but perhaps in other situations:
(//soap-env:Header/#filename , //soap-env:Header/#fileName, "default.dat")[1]
HTH!
You need to respect and take into account the SOAP XML namespace!
Since I don't know what you're using, I cannot tell you how to do this - but there's the xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" on the root node, and your #filename attribute is on the <soap-env:Header .... /> node - so you need to include the XML namespace in your XQuery.
In .NET / C#, you could do it like this (using the "older" XmlDocument style which supports XPath directly):
// define test XML
string xmlContent =
#"<?xml version='1.0'?>
<preFileDoc xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/'>
<senderId>ABC</senderId>
<receiverId>XYZ</receiverId>
<tranxCode>A001</tranxCode>
<inpXML version='1.0' encoding='UTF-8'>
<soap-env:Envelope>
<soap-env:Header msgcode='SPPCONVAKT' orig-system='002FTB' refid='65355ff50a172064484bf9da64c1e245' timestamp='2009-02-11 21:00:10.741' filename='SPPCONVAKT20090128001.dat'/>
<soap-env:Body>
text1
text2
</soap-env:Body>
</soap-env:Envelope>
</inpXML>
</preFileDoc>";
// create XmlDocument and load test data
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlContent);
// define XML namespace manager and add the SOAP namespace to it
XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
mgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
// use XPath and the XML namespaces to grab the <Header> node
// the first two nodes <preFileDoc> and <inpXML> are not inside any explicit
// XML namespace
// but the next two (<Envelope> and <Header>) are in the "soap" XML namespace
XmlNode header = doc.SelectSingleNode("/preFileDoc/inpXML/soap:Envelope/soap:Header", mgr);
// read the "filename" attribute from the header node
if(header != null && header.Attributes["filename"] != null)
{
string fileName = header.Attributes["filename"].Value;
}

Where do DAC objects live (in Classic ASP)?

I have taken over a departing programmer's Classic ASP object, and I'm trying to debug some DAC code. The trouble is I don't really understand what DAC is or how it works -- and I can't find were the DAC code "lives".
Here's the individual line of code:
set runObj = server.CreateObject("DAC.clsDb_container")
We use SQL Server, and I looked in Enterprise Manager under Stored Procedures and User-Defined functions, but I don't see anything named clsDB_container.
Any suggestions where I can find the code for this DAC object?
The full code in context:
FUNCTION getNewGUID
Dim runCON, runObj, runCMD
DebugWrite( "<BEGIN> iRoutines.asp|getNewGUID (a) GUID=" & GUID & " dealernum=" & dealernum )
set runObj = server.CreateObject("DAC.clsDb_container")
if not runObj.run_query("EXEC sproc_createGUID") then
traperror(runObj.DB_ErrStr)
else
GUID = replace(runObj.get_by_ordinal(0),"-","")
dealernum_len = trim(cstr(len(dealernum)))
set runObj = nothing
end if
getNewGUID = dealernum_len & dealernum & GUID
DebugWrite( "<END> iRoutines.asp|getNewGUID (b) getNewGUID=" & getNewGUID & " dealernum=" & dealernum )
END FUNCTION
This looks like a custom COM object that was written as a data access layer for the site.
The name of the object would be DAC.clsDb_container and lives in a DLL somewhere on the web server.
It is not standard - you will need to look for (I am guessing here) the VB6 or Delphi code that created it if you want to be enlightened further.
if all you need is a GUID then you could do this
<%
Function createGuid()
Set TypeLib = Server.CreateObject("Scriptlet.TypeLib")
dim tg : tg = TypeLib.Guid
createGuid = left(tg, len(tg)-2)
Set TypeLib = Nothing
End Function
%>

Using strings to access custom methods dynamically

I am creating a two-dimensional list that has consecutive numbers at the end of "day", for use as dataProvider for a DataGrid
i have been accessing them via the command
dg1.selectedItem.day1
dg1.selectedItem.day2
dg1.selectedItem.day3
etc...
is there any way to take the string ("day"+i) and convert it into a (what is it? variable name?)
so that i can do something along the lines of:
for(var i:Number=1; i<numFields; i++)
{
dg1.selectedIndex = i-1;
dg1.selectedItem.(mysteryFunction("day"+i)) = 42;
}
if there's a function that i could use for mysteryFunction, or what data type to use, any info would be very helpful
this is what i've been using (so tedious):
<mx:XMLList id="sched">
<field>
<day1></day1>
<day2></day2>
<day3></day3>
</field>
<field>
<day1></day1>
<day2></day2>
<day3></day3>
</field>
...
</mx:XMLList>
The "mystery function" you are looking for is as simple as indexing with brackets:
for(var i:Number=1; i<numFields; i++)
{
dg1.selectedIndex = i-1;
dg1.selectedItem["day"+i] = 42;
}
And it is called, surprisingly, an attribute.
Use an Array or if you are going to bind it (which I am kind of betting on) use ArrayCollection instead of naming these variables individually.
If the members are generated by some program, you better put all of these in one of the collection classes I mentioned above and then start the processing. It makes life easier in the long run.
E4X is the way to go when dealing with XML. The Mozilla guys have an arguably better explanation of that technology. So, if your XML is stored in a variable as:
var tree:XML = <field>
<day1></day1>
<day2></day2>
<day3></day3>
You can simply do:
tree.day1 = 42;
Why would you want this mysteryFunction()? A dataProvider object is just a collection of some Type. You know the type already, right? Read this.
Anyway, there is no such mystery function. Note, however, string concatenation with a number converts the number to a string. Try
trace("str " + 42);

Resources