XML validity testing again

I have a function that blows up on the last element in a for each loop. I tried to figure out how to implement TheEndless suggestion:

xmlcheck=m.xmlTrackList.track.GetNamedElements(“stream-url”)
if xmlcheck <> invalid and xmlcheck.count() >0

but the problem is, the result is zero so this fails, but the most of the records seem to be good, so I think I’m not checking the correct thing.

What I"m doing is requesting 20 records from the server and getting back 19. For some reason the 19th record crashes me to the debugger with:runtime error ec: ‘Dot’ Operator attempted with invalid BrightScript Component or interface reference.

180: xmlcheck=m.xmlTrackList.track.GetNamedElements(“stream-url”).[counter].GetText()

The idea of the above line was to try to validate the xml before calling the sub that actually does the assignment & posteritems so that I could leave

Any ideas would be helpful. I want to check each time through the for each loop if the data is good. If the problem is that there aren’t 19 records then wouldn’t the For Each only cycle the number of track in xmltracklist.track?

hope i’m making sense.

  • Joel

What is “.[counter]” in that line? That syntax seems odd…
Can you provide the code for the full loop?

counter is an integer that increments at the end of each loop.

print "ok well that didn't blow up so now do that parsing stuff"
	if m.xmlTracklist.Parse(m.xferResult)
		print "parse is true"
		counter=0   
		print "counter="+counter.ToStr()
		For Each track in m.xmlTrackList.track
			?"************xmlcheck***************"
		        xmlcheck=m.xmlTrackList.track.GetNamedElements("stream-url").[counter].GetText()
			?"***********************************"
			if xmlcheck <> invalid and xmlcheck <> "" then  'doesn't ever get this far on the last iteration because xmlcheck crashes
				?"xml check success"
				GetTrackSet(counter, track) ' this function is where it would die
				print m.TrackTitle+", "+m.TrackURL
				posterlist.push(CreatePosterItems())
				counter=counter+1
				print "counter="+counter.ToStr()
			else 
				?"xmlcheck failed"
			end if
	 	Next
	End If
	return posterlist
end function

function getTrackSet(counter as integer, track as object) as object
....
	 		m.TrackURL = m.xmlTrackList.track.GetNamedElements("stream-url").[counter].GetText()+m.ckey 'this line would blow up if it didn't blow up in the calling function
....

end function

I really don’t understand that [counter] part. And your outer look doesn’t makes sense either, since you’re not using “track”. Try this instead…

If m.xmlTracklist.Parse(m.xferResult) Then
    print "parse is true"
    counter=0     
    print "counter="+counter.ToStr()
    For Each track in m.xmlTrackList.track
        xmlStreamUrls = track.GetNamedElements("stream-url")
        If xmlstreamUrl <> invalid And xmlStreamUrls.Count() > 0 Then
            xmlcheck = xmlStreamUrls[ 0 ].GetText()
            if xmlcheck <> invalid and xmlcheck <> "" then    'doesn't ever get this far on the last iteration because xmlcheck crashes
                ?"xml check success"
                GetTrackSet(counter, track) ' this function is where it would die
                print m.TrackTitle+", "+m.TrackURL
                posterlist.push(CreatePosterItems())
                counter=counter+1
                print "counter="+counter.ToStr()
            else
                ?"xmlcheck failed"
            end if
        End If
    Next
End If

Also, why aren’t you passing the stream url to the getTrackSet function? Your code is inefficiently grabbing an roXMLList every time you call to those xml nodes, which isn’t necessary, since you looping through them to begin with. It seems to me, the [counter] part you have in there now is only working by chance. It could easily get out of sync if any one of the elements didn’t have a “stream-url” node.

“TheEndless” wrote:
Also, why aren’t you passing the stream url to the getTrackSet function? Your code is inefficiently grabbing an roXMLList every time you call to those xml nodes, which isn’t necessary, since you looping through them to begin with. It seems to me, the [counter] part you have in there now is only working by chance. It could easily get out of sync if any one of the elements didn’t have a “stream-url” node.

the stream url is in m.url which is accessable from anywhere, but the GetTrackSet function is where the streamurl is actually extracted from the xml which is acquired a few lines above what I pasted in - it is fully of print statements and rather hard to read, but here is the complete function as it stands now unmodified:

'***********************************************
'get searched for tracks 
'***********************************************

function getsearchTracks(ofs as integer, lim as integer, querystring as string) as object
posterlist=createobject("roarray",1,true)
m.audioplayer.clearcontent()
print "searchtracks="+querystring	
offset=ofs.tostr()
print "offset="+offset
limit=lim.tostr()
print "limit="+limit
print "assign url all that gnarly stuff for the query"
m.url=m.api+"/tracks.xml?q="+querystring+"&"+mid(m.ckey,2)+"&limit="+lim.tostr()+"&ofs="+ofs.tostr()
print "now set xfer url"
m.xfer.setURL(m.url)
print "now do the get to string thing"
m.xferResult= m.xfer.GetToString()
print "ok, skip printing result of transfer,  this may be where it blows up?"
'print "transfer-result"+m.xferResult
print "ok well that didn't blow up so now do that parsing stuff"
	if m.xmlTracklist.Parse(m.xferResult)
		print "parse is true blue"
		counter=0   
		print "counter="+counter.ToStr()
		For Each track in m.xmlTrackList.track
			?"************xmlcheck***************"
         		xmlcheck=m.xmlTrackList.track.GetNamedElements("stream-url").[counter].GetText()
			?"***********************************"
			if xmlcheck <> invalid and xmlcheck <> "" then
				?"xml check success"
				GetTrackSet(counter, track)
				print m.TrackTitle+", "+m.TrackURL
				posterlist.push(CreatePosterItems())
				counter=counter+1
				print "counter="+counter.ToStr()
			else 
				?"xmlcheck failed"
			end if
	 	Next
	End If
	return posterlist
end function

I understand that, but you’re calling GetTrackSet with parameters, and then parsing the XML again to pull the URL, which you just grabbed a few lines earlier. This seems redundant to me. You’re reprocessing the XML a lot in your code, for no real reason. Did you look at the code I posted?

Yes, I’m looking at the code you wrote right now. I have some questions:

“TheEndless” wrote:
I really don’t understand that [counter] part. And your outer look doesn’t makes sense either, since you’re not using “track”. Try this instead…

so in GetTrackSet:

function getTrackSet(counter as integer, track as object) as object
             m.TrackURL = m.xmlTrackList.track.GetNamedElements("stream-url").[counter].GetText()+m.ckey
             m.TrackGenre=m.xmlTrackList.track.GetNamedElements("genre").[counter].GetText()
             m.TrackDescription = m.xmlTrackList.track.GetNamedElements("description").[counter].GetText()
             m.TrackCreated=m.xmlTrackList.track.GetNamedElements("created-at").[counter].GetText()
             m.WaveformURL=m.xmlTracklist.track.GetNamedElements("waveform-url").[counter].GetText()+m.ckey
             m.ArtworkURL=m.xmlTrackList.track.GetNamedElements("artwork-url").[counter].GetText()
             if m.ArtworkURL="" then 
                 m.ArtworkURL="pkg:/images/sc-roku-poster.png" 
             else 
                 m.artworkURL=m.ArtworkURL+m.ckey
             end if
             print m.ArtworkURL
            
             print type(m.artworkURL)
              m.song.url = m.TrackURL+m.ckey
             m.song.StreamFormat="mp3"
             
             m.audioplayer.addcontent(m.song)
             return m.song
End Function

I can replace this:

 m.TrackURL = m.xmlTrackList.track.GetNamedElements("stream-url").[counter].GetText()+m.ckey

with this:

m.TrackURL = track.GetNamedElements("stream-url").GetText()+m.ckey

?

You should be able to, yeah.. with all of the GetNamedElements() calls. Your “counter” method is working (sorta), because you’re grabbing the n-th element in the list, but since you’re incrementing counter on every track, there’s a good possibility of it blowing up later in the loop, if any of the tracks don’t contain one of the elements you’re pulling. It’ll also get out of sync as soon as it hits a track without one of the elements… For example…

Given this XML…

<tracklist>
  <track>
    <stream-url>blah 1</stream-url>
    <genre>genre 1</genre>
    <description>description 1</description>
  </track>
  <track>
    <stream-url>blah 2</stream-url>
    <description>description 2</description>
  </track>
  <track>
    <stream-url>blah 3</stream-url>
    <genre>genre 3</genre>
    <description>description 1</description>
  </track>
</tracklist>

xmlTrackList.track.GetNamedElements(“stream-url”) is going to return an roXMLList with 3 elements (blah 1, blah 2, blah 3), whereas xmlTrackList.track.GetNamedElements(“genre”) is going to return an roXMLList with 2 elements (genre 1 and genre 3). So a call to the second element in each list (via your counter) is going to return the stream url for track 2, but the genre for track 3. A call to the 3rd element in each list will return the stream url for track 3, and blow up when it tries to get the 3rd genre.

By calling GetNamedElements() on the specific track (via your For…Next loop), you’ll limit those roXMLLists to elements beneath that track, so it will always have the correct value in index 0 (assuming the element exists on that track).

I’m wondering if there is a way to use encapsulation or something to create a method that would let me do an evaluation like this for all the returned xml so I don’t have to have a check like this for each xml tag that I’m retrieving?

something like

m.xferResult= m.xfer.GetToString()
if m.xmlTracklist.Parse(m.xferResult)
      
For Each track in m.xmltracklist.track
      taglist=track.extracttaglist() 'hypothetical function that I don't know how to write
      xmlcheck=track.validate(taglist) 'hypothetical function that I'm not quite sure how to write

where it would validate that every xml tag is valid in each iteration of the loop, instead of having an if statement for each known xml tag? What If I want to check all xmlTags for validity even if I don’t know what they are ahead of time?

here’s one idea for starters:


function validate(xmltag as string, obj as object) as boolean
	xmlcheck=obj.getnamedelements(xmltag) 
		return xmlcheck<>invalid and xmlcheck.count() >0
end function

What about a generic GetChildElementText() function?

Function GetChildElementText( parentElement As Object, childName As String, defaultValue = "" As String ) As String
    child = parentElement.GetNamedElements( childName )
    If child <> invalid and child.Count() > 0 Then
        Return child.GetText()
    Else
        Return defaultValue
    End If
End Function

Then your code can be simplified to:

m.TrackURL = GetChildElementText( track, "stream-url" )
m.TrackGenre = GetChildElementText( track, "genre" )
m.TrackDescription = GetChildElementText( track, "description" )
m.TrackCreated = GetChildElementText( track, "created-at" )
m.WaveformURL = GetChildElementText( track, "waveform-url" )
m.ArtworkURL = GetChildElementText( track, "artwork-url" )

Ok. What about a generic function that will do this for an unknown set of tags? So if I have m.xmltracklist, I could do something like:

taglist=extractTagList(m.xmltracklist)
for each tag in taglist
getChildElement(taglist.tag)
next

Since m.xmltracklist is an RoXMLElement, which appears to contain a list of roXMLElements:

BrightScript Debugger> ?type(m.xmltracklist)
roXMLElement
BrightScript Debugger> ?m.xmltracklist.GetChildElements()
ROT:roXMLElement
ROT:roXMLElement
ROT:roXMLElement
ROT:roXMLElement

which I assume represent each tag and its content, is there a way that you know of to get a list of all the tags in any given set of XML?

  • Joel

I think you just discovered it yourself :wink:
If you do a recursive loop through the roXMLElement.GetChildElements(), you can walk the entire document. GetName() should return the name of the element.

sort of:

v=track.GetChildElements()

BrightScript Debugger> ?v.GetName()
Member function not found in BrightScript Component or interface.

BrightScript Debugger> ?v[1].GetName()
created-at
BrightScript Debugger> ?v[2].GetName()
user-id

Don’t quite know what I would do a “for each” on… I can imagine doing: a

For i=1 to (v.count() -1)
? v
next

Any suggestions?

For Each node in xml.GetChildElements()
    print node.GetName()
Next

So if I understand this correctly, “node” is just a placeholder for “item” or “element” of an array, it doesn’t have any meaning with relationship to the actual names of the elements of the array?

  • Joel

Correct. It’s just a variable equal to the current child element in the loop.

Ok, thanks for explaining that!

  • Joel