Problem with parsing XML tag

Hello everyone,
I’m very new to Scala and I’m trying to parse a xml file to convert it into a csv in order to be furtherly processed by another scala program.
In this xml file, I have tags like this :

    <Event EventTime="2018-12-25T22:26:58" ProfileID="0031053794_0">
		<StreamEvent>
			<Stream streamId="160" streamType="Video"/>
			<Stream streamId="80" streamType="Audio"/>
			<Stream streamId="0" streamType="Data"/>
		</StreamEvent>
    </Event>

My concern is about catching the 3 stream ID in differents variables.

I’ve tried this code :

def getStreamId(Event: Node, StreamType: String) = {
 //To improve -- Using head , (1) and last can cause trouble if the order changes in the file

      if (StreamType == "Video") {
        val StreamID = (Event \ "_" \ "Stream" \\ "@streamId").head.text
        StreamID
      } else if (StreamType == "Audio") {
        val StreamID = (Event \ "_" \ "Stream" \\ "@streamId")(1).text
        StreamID
      } else {
        val StreamID = (Event \ "_" \ "Stream" \\ "@streamId").last.text
        StreamID
      }

    }

(I know this code is gonna give me the same value for each StreamType but I think about using a first part which will be like val Type = (Event \ "" \ “Stream” \ “@StreamType”).filter( == Streamtype) once it is working to have the correct value of each stream type.)
This one is working well but I’ve 2 problems with this code :

1)As I wrote in comment, this won’t work if the order aof the stream tags changes in the file
2)I also have Events where there is no tags. In this case, I got an error while running my code because “head”, “(1)” and “last” can be used on an empty object

That’s why I used this code :

def getStreamId(Event: Node, StreamType: String) = {

      if (StreamType == "Video") {
        val StreamID = (Event \ "_" \ "Stream" \\ "@streamId").text
        StreamID
      } else if (StreamType == "Audio") {
        val StreamID = (Event \ "_" \ "Stream" \\ "@streamId").text
        StreamID
      } else {
        val StreamID = (Event \ "_" \ "Stream" \\ "@streamId").text
        StreamID
      }

    }

The result of this code is : StreamID is the same for both case and equal to 160800 (then, the concatenation of each StreamID)

For those reasons, I’ve tried the following code :

if (StreamType == "Video") {
        val StreamID= (Event \ "_" \ "Stream" \ "@streamId").text
        StreamID
      } else if (StreamType == "Audio") {
        val StreamID = (Event \ "_" \ "Stream" \ "@streamId").text
        StreamID
      } else {
        val StreamID = (Event \ "_" \ "Stream" \ "@streamId").text
        StreamID
      }

Unfortunately, with this code, nothing is returned, even when there are tags. I can’t see where the problem is with this last code because I follow the exact order of the file tags.

I call the getStreamID function with :

val StreamId_Video = getStreamId(Event, "Video")
val StreamId_Audio = getStreamId(Event, "Audio")
val StreamId_Data = getStreamId(Event, "Data")

Then , my expected ouput is :
StreamId_Video = 160, StreamId_Audio = 80 and StreamId_Data = 0

Thanks for your help

Instead of using if for every possible value of streamType that you could search for and then going by the position, I would recommend the find method:

def getStreamId(Event: Node, StreamType: String): Option[String] = {
  (Event \ "_" \ "Stream")
    .find(_ \@ "streamType" == StreamType)
    .map(_ \@ "streamId")
}

The first line, (Event \ "_" \ "Stream") gives us a list of all Stream elements.

The find method on List gives you the first element matching some condition that you pass it as a function. In this example, we look for an element with the passed StreamType as value for that xml attribute. Note that we don’t have to hardcode the attribute values here.

The result of find is an Option[Node]. So if we do not find a <Stream> tag with matching streamType, the result is None, otherwise it’s the node wrapped in a Some. Using map we can apply a function to the wrapped node, getting the streamId attribute. If we did not find a <Stream> tag before, the result will still be None.

The return type of this implementation is now Option[String] instead of String as it is with yours. This means you have to check if there actually was such a tag. Tou could for example use getOrElse to provide a default value, if this is plausible for your application, or throw an error if they are strictly required. As you said that the tags may be missing, encoding this in the method signature via the Option type is good practice.

Usage examples:

// default value for missing stream
val StreamId_Video = getStreamId(Event, "Video").getOrElse("-1")
// exception for missing stream
val StreamId_Video = getStreamId(Event, "Video").getOrElse(throw new Exception("missing stream"))
1 Like

Thank you for your reply, I’m gonna test right now and let you know if that worked. I already knew about the getOrElse function cause I need it in other functions but didn’t think about use it here.

It worked perfectly, thank you very much @crater2150