content: Function simpleJSONParser( jsonString As String ) As Object
          q = chr(34)
  
          beforeKey  = "[,{]"
          keyFiller  = "[^:]*?"
          keyNospace = "[-_\w\d]+"
          valueStart = "[" +q+ "\d\[{]|true|false|null"
          reReplaceKeySpaces = "("+beforeKey+")\s*"+q+"("+keyFiller+")("+keyNospace+")\s+("+keyNospace+")\s*"+q+"\s*:\s*(" + valueStart + ")"
  
          regexKeyUnquote = CreateObject( "roRegex", q + "([a-zA-Z0-9_\-\s]*)" + q + "\:", "i" )
          regexKeyUnspace = CreateObject( "roRegex", reReplaceKeySpaces, "i" )
          regexQuote = CreateObject( "roRegex", "\\" + q, "i" )
  
          ' setup "null" variable
          null = invalid
  
          ' Replace escaped quotes
          jsonString = regexQuote.ReplaceAll( jsonString, q + " + q + " + q )
  
          while regexKeyUnspace.isMatch( jsonString )
                  jsonString = regexKeyUnspace.ReplaceAll( jsonString, "\1"+q+"\2\3\4"+q+": \5" )
          end while
  
          jsonString = regexKeyUnquote.ReplaceAll( jsonString, "\1:" )
  
          jsonObject = invalid
          ' Eval the BrightScript formatted JSON string
          Eval( "jsonObject = " + jsonString )
          Return jsonObject
  End Function
  
  '******************************************************
  'Registry Helper Functions
  '******************************************************
  Function RegRead(key, section=invalid)
      if section = invalid then section = "Default"
      sec = CreateObject("roRegistrySection", section)
      if sec.Exists(key) then return sec.Read(key)
      return invalid
  End Function
  
  Function RegWrite(key, val, section=invalid) As Boolean
      if section = invalid then section = "Default"
      sec = CreateObject("roRegistrySection", section)
      sec.Write(key, val)
      return sec.Flush()
  End Function
  
  Function RegDelete(key, section=invalid) As Boolean
      if section = invalid then section = "Default"
      sec = CreateObject("roRegistrySection", section)
      sec.Delete(key)
      return sec.Flush()
  End Function
  
  
  '******************************************************
  'Insertion Sort
  'Will sort an array directly, or use a key function
  '******************************************************
  Sub Sort(A as Object, key=invalid as dynamic)
  
      if type(A)<>"roArray" then return
  
      if (key=invalid) then
          for i = 1 to A.Count()-1
              value = A[i]
              j = i-1
              while j>= 0 and A[j] > value
                  A[j + 1] = A[j]
                  j = j-1
              end while
              A[j+1] = value
          next
  
      else
          if type(key)<>"Function" then return
          for i = 1 to A.Count()-1
              valuekey = key(A[i])
              value = A[i]
              j = i-1
              while j>= 0 and key(A[j]) > valuekey
                  A[j + 1] = A[j]
                  j = j-1
              end while
              A[j+1] = value
          next
  
      end if
  
  End Sub
  
  
  '******************************************************
  'Convert anything to a string
  '
  'Always returns a string
  '******************************************************
  Function tostr(any)
      ret = AnyToString(any)
      if ret = invalid ret = type(any)
      if ret = invalid ret = "unknown" 'failsafe
      return ret
  End Function
  
  
  '******************************************************
  'Get a " char as a string
  '******************************************************
  Function Quote()
      q$ = Chr(34)
      return q$
  End Function
  
  
  '******************************************************
  'isxmlelement
  '
  'Determine if the given object supports the ifXMLElement interface
  '******************************************************
  Function isxmlelement(obj as dynamic) As Boolean
      if obj = invalid return false
      if GetInterface(obj, "ifXMLElement") = invalid return false
      return true
  End Function
  
  
  '******************************************************
  'islist
  '
  'Determine if the given object supports the ifList interface
  '******************************************************
  Function islist(obj as dynamic) As Boolean
      if obj = invalid return false
      if GetInterface(obj, "ifArray") = invalid return false
      return true
  End Function
  
  
  '******************************************************
  'isint
  '
  'Determine if the given object supports the ifInt interface
  '******************************************************
  Function isint(obj as dynamic) As Boolean
      if obj = invalid return false
      if GetInterface(obj, "ifInt") = invalid return false
      return true
  End Function
  
  '******************************************************
  ' validstr
  '
  ' always return a valid string. if the argument is
  ' invalid or not a string, return an empty string
  '******************************************************
  Function validstr(obj As Dynamic) As String
      if isnonemptystr(obj) return obj
      return ""
  End Function
  
  
  '******************************************************
  'isstr
  '
  'Determine if the given object supports the ifString interface
  '******************************************************
  Function isstr(obj as dynamic) As Boolean
      if obj = invalid return false
      if GetInterface(obj, "ifString") = invalid return false
      return true
  End Function
  
  
  '******************************************************
  'isnonemptystr
  '
  'Determine if the given object supports the ifString interface
  'and returns a string of non zero length
  '******************************************************
  Function isnonemptystr(obj)
      if isnullorempty(obj) return false
      return true
  End Function
  
  
  '******************************************************
  'isnullorempty
  '
  'Determine if the given object is invalid or supports
  'the ifString interface and returns a string of non zero length
  '******************************************************
  Function isnullorempty(obj)
      if obj = invalid return true
      if not isstr(obj) return true
      if Len(obj) = 0 return true
      return false
  End Function
  
  
  '******************************************************
  'isbool
  '
  'Determine if the given object supports the ifBoolean interface
  '******************************************************
  Function isbool(obj as dynamic) As Boolean
      if obj = invalid return false
      if GetInterface(obj, "ifBoolean") = invalid return false
      return true
  End Function
  
  
  '******************************************************
  'isfloat
  '
  'Determine if the given object supports the ifFloat interface
  '******************************************************
  Function isfloat(obj as dynamic) As Boolean
      if obj = invalid return false
      if GetInterface(obj, "ifFloat") = invalid return false
      return true
  End Function
  
  
  '******************************************************
  'strtobool
  '
  'Convert string to boolean safely. Don't crash
  'Looks for certain string values
  '******************************************************
  Function strtobool(obj As dynamic) As Boolean
      if obj = invalid return false
      if type(obj) <> "roString" and type(obj) <> "String" return false
      o = strTrim(obj)
      o = Lcase(o)
      if o = "true" return true
      if o = "t" return true
      if o = "y" return true
      if o = "1" return true
      return false
  End Function
  
  
  '******************************************************
  'itostr
  '
  'Convert int to string. This is necessary because
  'the builtin Stri(x) prepends whitespace
  '******************************************************
  Function itostr(i As Integer) As String
      str = Stri(i)
      return strTrim(str)
  End Function
  
  
  '******************************************************
  'Get remaining hours from a total seconds
  '******************************************************
  Function hoursLeft(seconds As Integer) As Integer
      hours% = seconds / 3600
      return hours%
  End Function
  
  
  '******************************************************
  'Get remaining minutes from a total seconds
  '******************************************************
  Function minutesLeft(seconds As Integer) As Integer
      hours% = seconds / 3600
      mins% = seconds - (hours% * 3600)
      mins% = mins% / 60
      return mins%
  End Function
  
  
  '******************************************************
  'Pluralize simple strings like "1 minute" or "2 minutes"
  '******************************************************
  Function Pluralize(val As Integer, str As String) As String
      ret = itostr(val) + " " + str
      if val <> 1 ret = ret + "s"
      return ret
  End Function
  
  
  '******************************************************
  'Trim a string
  '******************************************************
  Function strTrim(str As String) As String
      st=CreateObject("roString")
      st.SetString(str)
      return st.Trim()
  End Function
  
  
  '******************************************************
  'Tokenize a string. Return roList of strings
  '******************************************************
  Function strTokenize(str As String, delim As String) As Object
      st=CreateObject("roString")
      st.SetString(str)
      return st.Tokenize(delim)
  End Function
  
  
  '******************************************************
  'Replace substrings in a string. Return new string
  '******************************************************
  Function strReplace(basestr As String, oldsub As String, newsub As String) As String
      newstr = ""
  
      i = 1
      while i <= Len(basestr)
          x = Instr(i, basestr, oldsub)
          if x = 0 then
              newstr = newstr + Mid(basestr, i)
              exit while
          endif
  
          if x > i then
              newstr = newstr + Mid(basestr, i, x-i)
              i = x
          endif
  
          newstr = newstr + newsub
          i = i + Len(oldsub)
      end while
  
      return newstr
  End Function
  
  
  '******************************************************
  'Get all XML subelements by name
  '
  'return list of 0 or more elements
  '******************************************************
  Function GetXMLElementsByName(xml As Object, name As String) As Object
      list = CreateObject("roArray", 100, true)
      if islist(xml.GetBody()) = false return list
  
      for each e in xml.GetBody()
          if e.GetName() = name then
              list.Push(e)
          endif
      next
  
      return list
  End Function
  
  
  '******************************************************
  'Get all XML subelement's string bodies by name
  '
  'return list of 0 or more strings
  '******************************************************
  Function GetXMLElementBodiesByName(xml As Object, name As String) As Object
      list = CreateObject("roArray", 100, true)
      if islist(xml.GetBody()) = false return list
  
      for each e in xml.GetBody()
          if e.GetName() = name then
              b = e.GetBody()
              if type(b) = "roString" or type(b) = "String" list.Push(b)
          endif
      next
  
      return list
  End Function
  
  
  '******************************************************
  'Get first XML subelement by name
  '
  'return invalid if not found, else the element
  '******************************************************
  Function GetFirstXMLElementByName(xml As Object, name As String) As dynamic
      if islist(xml.GetBody()) = false return invalid
  
      for each e in xml.GetBody()
          if e.GetName() = name return e
      next
  
      return invalid
  End Function
  
  
  '******************************************************
  'Get first XML subelement's string body by name
  '
  'return invalid if not found, else the subelement's body string
  '******************************************************
  Function GetFirstXMLElementBodyStringByName(xml As Object, name As String) As dynamic
      e = GetFirstXMLElementByName(xml, name)
      if e = invalid return invalid
      if type(e.GetBody()) <> "roString" and type(e.GetBody()) <> "String" return invalid
      return e.GetBody()
  End Function
  
  
  '******************************************************
  'Get the xml element as an integer
  '
  'return invalid if body not a string, else the integer as converted by strtoi
  '******************************************************
  Function GetXMLBodyAsInteger(xml As Object) As dynamic
      if type(xml.GetBody()) <> "roString" and type(xml.GetBody()) <> "String" return invalid
      return strtoi(xml.GetBody())
  End Function
  
  
  '******************************************************
  'Parse a string into a roXMLElement
  '
  'return invalid on error, else the xml object
  '******************************************************
  Function ParseXML(str As String) As dynamic
      if str = invalid return invalid
      xml=CreateObject("roXMLElement")
      if not xml.Parse(str) return invalid
      return xml
  End Function
  
  
  '******************************************************
  'Get XML sub elements whose bodies are strings into an associative array.
  'subelements that are themselves parents are skipped
  'namespace :'s are replaced with _'s
  '
  'So an XML element like...
  '
  '<blah>
  '    <This>abcdefg</This>
  '    <Sucks>xyz</Sucks>
  '    <sub>
  '        <sub2>
  '        ....
  '        </sub2>
  '    </sub>
  '    <ns:doh>homer</ns:doh>
  '</blah>
  '
  'returns an AA with:
  '
  'aa.This = "abcdefg"
  'aa.Sucks = "xyz"
  'aa.ns_doh = "homer"
  '
  'return an empty AA if nothing found
  '******************************************************
  Sub GetXMLintoAA(xml As Object, aa As Object)
      for each e in xml.GetBody()
          body = e.GetBody()
          if type(body) = "roString" or type(body) = "String" then
              name = e.GetName()
              name = strReplace(name, ":", "_")
              aa.AddReplace(name, body)
          endif
      next
  End Sub
  
  
  '******************************************************
  'Walk an AA and print it
  '******************************************************
  Sub PrintAA(aa as Object)
      print "---- AA ----"
      if aa = invalid
          print "invalid"
          return
      else
          cnt = 0
          for each e in aa
              x = aa[e]
              PrintAny(0, e + ": ", aa[e])
              cnt = cnt + 1
          next
          if cnt = 0
              PrintAny(0, "Nothing from for each. Looks like :", aa)
          endif
      endif
      print "------------"
  End Sub
  
  
  '******************************************************
  'Walk a list and print it
  '******************************************************
  Sub PrintList(list as Object)
      print "---- list ----"
      PrintAnyList(0, list)
      print "--------------"
  End Sub
  
  
  '******************************************************
  'Print an associativearray
  '******************************************************
  Sub PrintAnyAA(depth As Integer, aa as Object)
      for each e in aa
          x = aa[e]
          PrintAny(depth, e + ": ", aa[e])
      next
  End Sub
  
  
  '******************************************************
  'Print a list with indent depth
  '******************************************************
  Sub PrintAnyList(depth As Integer, list as Object)
      i = 0
      for each e in list
          PrintAny(depth, "List(" + itostr(i) + ")= ", e)
          i = i + 1
      next
  End Sub
  
  
  '******************************************************
  'Print anything
  '******************************************************
  Sub PrintAny(depth As Integer, prefix As String, any As Dynamic)
      if depth >= 10
          print "**** TOO DEEP " + itostr(5)
          return
      endif
      prefix = string(depth*2," ") + prefix
      depth = depth + 1
      str = AnyToString(any)
      if str <> invalid
          print prefix + str
          return
      endif
      if type(any) = "roAssociativeArray"
          print prefix + "(assocarr)..."
          PrintAnyAA(depth, any)
          return
      endif
      if islist(any) = true
          print prefix + "(list of " + itostr(any.Count()) + ")..."
          PrintAnyList(depth, any)
          return
      endif
  
      print prefix + "?" + type(any) + "?"
  End Sub
  
  
  '******************************************************
  'Print an object as a string for debugging. If it is
  'very long print the first 500 chars.
  '******************************************************
  Sub Dbg(pre As Dynamic, o=invalid As Dynamic)
      p = AnyToString(pre)
      if p = invalid p = ""
      if o = invalid o = ""
      s = AnyToString(o)
      if s = invalid s = "???: " + type(o)
      if Len(s) > 4000
          s = Left(s, 4000)
      endif
      print p + s
  End Sub
  
  
  '******************************************************
  'Try to convert anything to a string. Only works on simple items.
  '
  'Test with this script...
  '
  '    s$ = "yo1"
  '    ss = "yo2"
  '    i% = 111
  '    ii = 222
  '    f! = 333.333
  '    ff = 444.444
  '    d# = 555.555
  '    dd = 555.555
  '    bb = true
  '
  '    so = CreateObject("roString")
  '    so.SetString("strobj")
  '    io = CreateObject("roInt")
  '    io.SetInt(666)
  '    tm = CreateObject("roTimespan")
  '
  '    Dbg("", s$ ) 'call the Dbg() function which calls AnyToString()
  '    Dbg("", ss )
  '    Dbg("", "yo3")
  '    Dbg("", i% )
  '    Dbg("", ii )
  '    Dbg("", 2222 )
  '    Dbg("", f! )
  '    Dbg("", ff )
  '    Dbg("", 3333.3333 )
  '    Dbg("", d# )
  '    Dbg("", dd )
  '    Dbg("", so )
  '    Dbg("", io )
  '    Dbg("", bb )
  '    Dbg("", true )
  '    Dbg("", tm )
  '
  'try to convert an object to a string. return invalid if can't
  '******************************************************
  Function AnyToString(any As Dynamic) As dynamic
      if any = invalid return "invalid"
      if isstr(any) return any
      if isint(any) return itostr(any)
      if isbool(any)
          if any = true return "true"
          return "false"
      endif
      if isfloat(any) return Str(any)
      if type(any) = "roTimespan" return itostr(any.TotalMilliseconds()) + "ms"
      return invalid
  End Function
  
  
  '******************************************************
  'Walk an XML tree and print it
  '******************************************************
  Sub PrintXML(element As Object, depth As Integer)
      print tab(depth*3);"Name: [" + element.GetName() + "]"
      if invalid <> element.GetAttributes() then
          print tab(depth*3);"Attributes: ";
          for each a in element.GetAttributes()
              print a;"=";left(element.GetAttributes()[a], 4000);
              if element.GetAttributes().IsNext() then print ", ";
          next
          print
      endif
  
      if element.GetBody()=invalid then
          ' print tab(depth*3);"No Body"
      else if type(element.GetBody())="roString" or type(element.GetBody())="String" then
          print tab(depth*3);"Contains string: [" + left(element.GetBody(), 4000) + "]"
      else
          print tab(depth*3);"Contains list:"
          for each e in element.GetBody()
              PrintXML(e, depth+1)
          next
      endif
      print
  end sub
  
  
  '******************************************************
  'Dump the bytes of a string
  '******************************************************
  Sub DumpString(str As String)
      print "DUMP STRING"
      print "---------------------------"
      print str
      print "---------------------------"
      l = Len(str)-1
      i = 0
      for i = 0 to l
          c = Mid(str, i)
          val = Asc(c)
          print itostr(val)
      next
      print "---------------------------"
  End Sub
  
  
  '******************************************************
  'Validate parameter is the correct type
  '******************************************************
  Function validateParam(param As Object, paramType As String,functionName As String, allowInvalid = false) As Boolean
      if paramType = "roString" or paramType = "String" then
          if type(param) = "roString" or type(param) = "String" then
              return true
          end if
      else if type(param) = paramType then
          return true
      endif
  
      if allowInvalid = true then
          if type(param) = invalid then
              return true
          endif
      endif
  
      print "invalid parameter of type "; type(param); " for "; paramType; " in function "; functionName
      return false
  End Function
  
  
  Sub LogAnalytics(pageTitle, pageURL, userValue)
  	GATrackPageView(pageTitle, "/" + pageURL, userValue)
  End Sub
  
  Sub LogAnalyticsEvent(pageTitle, pageURL, userValue, eventCategory, eventAction, eventLabel, eventValue)
  	GATrackEvent(pageTitle, "/" + pageURL, userValue, eventCategory, eventAction, eventLabel, eventValue)
  End Sub
  
  Function InitGATracker(AccountID As String, HostName As String)
      resinfo = CreateObject("roDeviceInfo")
      m.GATracker = CreateObject("roAssociativeArray")
      m.GATracker.Account     = AccountID 'The Google Account ID(s) (utmac)
      m.GATracker.Version     = "1" 'The version of ga.js (utmwv)
      If Len(HostName) > 0 Then
          m.GATracker.HostName = HostName 'Domain (utmhn)
      Else
          m.GATracker.HostName = "roku.xxx.com" 'Domain (utmhn)
      End If
      m.GATracker.Language    = "en-us" 'Language of browser (utmul)
      m.GATracker.Resolution  = resinfo.GetDisplayMode() + " " + resinfo.GetDisplayType() 'Resolution and TV type (utmsr)
      m.GATracker.ColorDepth  = "24-bit" 'Color depth of display (utmsc)
      m.GATracker.Debug       = False 'Set debug printing to false
      m.GATracker.Pretend     = False 'Set pretend mode to false
  End Function
  
  Function GATrackPageView(PageTitle As String, PageURL As String, UserVal As String)
      GATrackEvent(PageTitle, PageURL, UserVal, "", "", "", "")
  End Function
  
  Function GATrackEvent(PageTitle As String, PageURL As String, UserVal As String, ECategory As String, EAction As String, ELabel As String, EValue As String)
      If Len(m.GATracker.Account) = 0 Then
          If m.GATracker.Debug Then Print "GA Tracker Error: No GA account id specified"
          Return 0
      End IF
      If Len(ECategory) > 0 And Len(EAction) > 0 Then
          is_event = True
      Else
          is_event = False
      End If
      PA = Len(PageTitle): PB = Len(PageURL)
      If PA = 0 And PB > 0 Then PageTitle = PageURL
      If PB = 0 And PA > 0 Then PageURL = PageTitle
      If PA = 0 And PB = 0 Then
          PageTitle = "Roku": PageURL = "Roku"
      End If
      If Len(UserVal) = 0 Then UserVal = "Roku"
  
      timestamp = CreateObject("roDateTime")
      xfer = CreateObject("roURLTransfer")
  
      var_utmn    = GARandNumber(1000000000,9999999999).ToStr()   'Random Request Number
      var_cookie  = GARandNumber(1000000000,9999999999).ToStr()   'Random Cookie Number
      var_random  = GARandNumber(1000000000,2147483647).ToStr()   'Random Number Under 2147483647
      var_today   = timestamp.asSeconds().ToStr()                 'Unix Timestamp For Current Date
  
      urchin_url = "http://www.google-analytics.com/__utm.gif?utmwv=1&utmn=" + var_utmn
      urchin_url = urchin_url + "&utmsr=" + xfer.Escape(m.GATracker.Resolution)
      urchin_url = urchin_url + "&utmsc=24-bit&utmul=" + xfer.Escape(m.GATracker.Language)
      urchin_url = urchin_url + "&utmje=0&utmfl=-&utmdt=" + xfer.Escape(PageTitle)
      urchin_url = urchin_url + "&utmhn=" + xfer.Escape(m.GATracker.HostName)
      urchin_url = urchin_url + "&utmr=-&utmp=" + xfer.Escape(PageURL)
      urchin_url = urchin_url + "&utmac=" + m.GATracker.Account
      urchin_url = urchin_url + "&utmcc=__utma%3D" + var_cookie
      urchin_url = urchin_url + "." + var_random + "." + var_today + "." + var_today + "." + var_today
      urchin_url = urchin_url + ".2%3B%2B__utmb%3D" + var_cookie
      urchin_url = urchin_url + "%3B%2B__utmc%3D" + var_cookie
      urchin_url = urchin_url + "%3B%2B__utmz%3D" + var_cookie
      urchin_url = urchin_url + "." + var_today
      urchin_url = urchin_url + ".2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D" + var_cookie
      urchin_url = urchin_url + "." + xfer.Escape(UserVal) + "%3B"
      If is_event Then
          urchin_url = urchin_url + "&utme=5(" + xfer.Escape(ECategory) + "*" + xfer.Escape(EAction)
          If Len(ELabel) > 0 Then urchin_url = urchin_url + "*" + xfer.Escape(ELabel)
          If Len(EValue) > 0 Then urchin_url = urchin_url + ")(" + xfer.Escape(EValue)
          urchin_url = urchin_url + ")&utmt=event"
      End If
  
      If m.GATracker.Pretend Then
          response = "In pretend mode"
      Else
          xfer.SetURL(urchin_url)
          response = xfer.GetToString()
      End If
      If m.GATracker.Debug Then
          If is_event Then
              Print "GA Tracker: Sent an event with response: " + response
          Else
              Print "GA Tracker: Sent a page view with response: " + response
          End If
          Print "GA Tracker: URL - " + urchin_url
      End If
  End Function
  
  Function GARandNumber(num_min As Integer, num_max As Integer) As Integer
      Return (RND(0) * (num_max - num_min)) + num_min
  End Function
  
  
  