XML Parser - Find Node based on Attribute Value

Started by Josh Fake, March 29, 2019, 10:19:05 AM

Previous topic - Next topic

Josh Fake

To All:

I am in the process of moving away from the MS XML Parser MSXML2.DomDocument and replacing with the native XML object parser *obj/XML.  95% of the code is easily interpreted from using the MS COM Object to the Pvx Parser, but this last issue.

I have an incoming XML response from a webservice (this is just a "small snippet") with a list of a very large of Question Subnodes within the Question Node.

<Questions Level="Product">
    <Question Name="SpecialtyShape">
        <Answer>None</Answer>
    </Question>
    <Question Name="MV_ConfigIDListVisual">
        <Answer>1340</Answer>
        <Answer>1348</Answer>
    </Question>
    <Question Name="PricingGroup">
        <Answer>L06</Answer>
    </Question>
    <Question Name="Shape">
        <Answer>Rectangular</Answer>
    </Question>
    <Question Name="InsectScreen">
        <Answer>No</Answer>
    </Question>
    <Question Name="ConfigIDAccessoryGrid">
        <Answer>-29</Answer>
    </Question>
    <Question Name="NetPriceExtended">
        <Answer>2951.10</Answer>
    </Question>
    <Question Name="InsectScreenMaterial">
        <Answer>N/A</Answer>
    </Question>
</Questions>

Within the MSXML DOM Document I could easily find a Question based on the Name attributes value.
shape_node=xml'selectsinglenode("Questions/Question[@Name=""Shape""]")
shape$=shape_node'getelementsbytagname(""Answer"")'item(0)'text$

Is there any method within the PxPlus XML object to find a node based on an attributes' value as it can be achieved in the MS XML Object?
I could loop through all questions (almost 200) and create a memory file using the Name of the Question as the primary key, but that would involve more changes than I was hoping to make, I justed was hoping to replace the object so the code was not limited to a windows o/s.

Thanks for any and all help ahead of time

Allen Miglore

You can use the xml() function to loop through the xml nodes, and the attribute nodes as well.

open (unt,isz=1)"test.xml"
read record (lfo,siz=9999999)xml$
close (lfo)
let ofs=0
let xml1$=xml(next from xml$,ind=ofs,key=tag$) ! get first level xml
let ofs1=0
while 1
let xml2$=xml(next from xml1$,ind=ofs1,key=tag$,opt=prop$,err=*break) ! loop through tags 
let propofs=0
while 1
let propval$=xml(property next from prop$,ind=propofs,key=propname$,err=*break) ! loop through attributes until Name="Shape"
if propname$="Name" and propval$="Shape" then let found=1; break
wend
if found then break
wend
print xml2$

Josh Fake

Thank you Allen!

I will try this as I have never worked directly with the xml() function and see if this will work.  I have about 30 of these I have to navigate through so I will probably have to add a switch/case/end switch while transversing through all the question sub-nodes.  But I will definitely try this.

I was just hoping there would be a way to handle this via the xml class find_node() method.  Most of the methods I use in the MS Com Object are easily converted to a method within the*obj/XML interface with the exception of this one. I'll add this as an "enhancement request".

Allen Miglore

We have a similar attribute search functionality in the UnForm xmlreader object, which internally is using the pxplus xml() function.  If the customer has UnForm, like a lot of yours do, you could write a simple job that uses that object.

Josh Fake

Thanks Allen, this client uses Unform extensively.   I might check it out as I could use your REST interfaces to post the Question Node and have it return a response back to FACTS as a new alternative.

Mike King

Actually the easiest solution might be to use the *obj/xml object we supply.

You can load it with the contents of the XML file then use Find_Node to find desired node/attribute.

Here is the code based on the XML you provided having been put in the file test.xml:

0010 LET oXml=NEW("*obj/xml" FOR PROGRAM) ! The FOR PROGRAM drops when done
0020 OPEN (1,ISZ=-1)"test.xml"
0030 READ RECORD (1,SIZ=1000000)R$ ! Read whole file into string
0040 CLOSE (1)
0050 oXml'set_xml(R$)
0060 LET oNode=0
0070 LET oQuestions=oXml'Find_node("Questions")
0080 WHILE 1
0090 LET oNode=oQuestions'Find_node("Question",oNode)
0100 IF oNode=0 THEN BREAK
0110 IF oNode'Find_Attr("Name")'Value$="Shape" THEN BREAK
0120 WEND
0130 IF oNode=0 THEN PRINT "not found" ELSE PRINT oNode'Find_node("Answer")'Value$


The steps were:

  • Create the XML object
  • Read the whole file into memory (used a dummy large SIZ= to assure whole file)
  • Passed file to Set_xml method
  • Found the Questions Node
  • Loop through each Question looking for any question with attribute of "Name" = "Shape"
  • Printed our the value of the 'Answer" node for that Question.


Mike King
President - BBSysco Consulting - http://www.bbsysco.com
eMail: mike.king@bbsysco.com

Peter.Higgins

Josh,
Since PVX has object inheritance, I would suggest a re-class of the xml object to add Mikes attribute search function so the code is reusable and not in the edited programs.   If the process does a large amount of searching in large files, adding an override of the *obj/xml parser routine to add a memory file with alternate keys on the attributes etc would be warranted.