Thread: Parsing HTML...or UPnP?

  1. #1
    Registered User
    Join Date
    Feb 2005
    Posts
    16

    Parsing HTML...or UPnP?

    Hi all. I'm trying to create a program that will allow me to view what a UPnP audio device is playing and (eventually) control that.

    I can see a full rundown of all the UPnP information in an XML file that is available at a URL (http://x.x.x.x/xml/zone_player.xml) and is also accessible via intel's UPnP tools.

    What is the best way to connect to the device and get information (eg. song currently playing)? As far as I can see, I could either parse the XML file, or connect via UPnP. UPnP would be best, as when I get to the point of controlling the device, simply reading an XML file wouldn't help...

    I guess what I really would like to know is, where do I start. I'm pretty new to C, at the moment I've figured I need to initiate an outgoing connection with socket() and so forth - is there a better way to do it? Do I even need to do that? All the documentation on UPnP I can find seems to be for other languages.

    Edit: Come to think of it, this is probably in the wrong forum. Can a moderator move it?
    Last edited by crummy; 02-16-2005 at 01:26 PM.

  2. #2
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    Hi, what OS and compiler are you using? Windows ME and XP include a UPnP API. Since using this API from C or C++ is quite complicated, it is worth checking if the API will work by testing with a simpler script. Here is a VBScript. Copy and paste it to a text file, save it as "upnp.vbs" and double click to run. It should give you a list of devices and services exposed by the description document.
    Code:
    Dim upDescriptionDoc, upDevice, upService
    
    Set upDescriptionDoc = CreateObject("UPnP.DescriptionDocument")
    upDescriptionDoc.Load("http://x.x.x.x/xml/zone_player.xml")
    
    Set upDevice = upDescriptionDoc.RootDevice()
    WScript.Echo "Device: " & upDevice.Type & "\" & _
                              upDevice.Description & vbCrLf
    
    For Each upService In upDevice.Services
        WScript.Echo "   Service: " & upService.ServiceTypeIdentifier & "\" & _
                                      upService.Id & vbCrLf
    Next
    If that works, the next step is to call a method on a service object. You need to look up the list of methods and their arguments in the XML file so you can call one. If you can attach the XML file, I can probably give you some hints on how to call a method.

    Creating your own UPnP stack using sockets would obviously be a lot more complicated.

  3. #3
    Registered User
    Join Date
    Feb 2005
    Posts
    16
    Hi there, thanks for replying! Didn't realise anyone saw my thread, thought it got lost in the masses of threads you get here...

    That VB script errors. Line 4, char 1, error 0x80072EFD. Source (null). Hope that helps...

    Anyway, I'm running XP Pro here. After exploring Intel's UPnP tools a bit more, it seems that all I need to do is send an HTTP request (with GET or POST) with the right XML code in it. I think I can do that with functions in winhttp.h, is that right? HttpWebConnect(); or something similar?

    Edit: Here's the code I stole off MSDN for what I beleive is initiating a web request. I can't seem to get it to compile; its made for Microsoft's compiler maybe? I'm swapping between Dev-C++ and LCC. I've already modified a bit of code to prevent a few errors, hopefully I didn't break anything while I did...

    Code:
    #include <windows.h>
    #include <winhttp.h>
    #include <stdio.h>
    
    int main(void) {
      DWORD dwSize = 0;
      DWORD dwDownloaded = 0;
      LPSTR pszOutBuffer;
      BOOL  bResults = FALSE;
      HINTERNET  hSession = NULL,
                 hConnect = NULL,
                 hRequest = NULL;
    
      // Use WinHttpOpen to obtain a session handle.
      hSession = WinHttpOpen( L"WinHTTP Example/1.0",
                              WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                              WINHTTP_NO_PROXY_NAME,
                              WINHTTP_NO_PROXY_BYPASS, 0 );
    
      // Specify an HTTP server.
      if( hSession )
        hConnect = WinHttpConnect( hSession, L"www.microsoft.com",
                                   INTERNET_DEFAULT_HTTPS_PORT, 0 );
    
      // Create an HTTP request handle.
      if( hConnect )
        hRequest = WinHttpOpenRequest( hConnect, L"GET", NULL,
                                       NULL, WINHTTP_NO_REFERER,
                                       WINHTTP_DEFAULT_ACCEPT_TYPES,
                                       WINHTTP_FLAG_SECURE );
    
      // Send a request.
      if( hRequest )
        bResults = WinHttpSendRequest( hRequest,
                                       WINHTTP_NO_ADDITIONAL_HEADERS, 0,
                                       WINHTTP_NO_REQUEST_DATA, 0,
                                       0, 0 );
    
    
      // End the request.
      if( bResults )
        bResults = WinHttpReceiveResponse( hRequest, NULL );
    
      // Keep checking for data until there is nothing left.
      if( bResults )
      {
        do
        {
          // Check for available data.
          dwSize = 0;
          if( !WinHttpQueryDataAvailable( hRequest, &dwSize ) )
            printf( "Error %u in WinHttpQueryDataAvailable.\n",
                    GetLastError( ) );
    
          // Allocate space for the buffer.
          char pszOutBuffer;
          pszOutBuffer = dwSize+1;
          if( !pszOutBuffer )
          {
            printf( "Out of memory\n" );
            dwSize=0;
          }
          else
          {
            // Read the data.
    		int newsize;
    		newsize = dwSize+1;
            ZeroMemory( pszOutBuffer, newsize );
    
            if( !WinHttpReadData( hRequest, (LPVOID)pszOutBuffer,
                                  dwSize, &dwDownloaded ) )
              printf( "Error %u in WinHttpReadData.\n", GetLastError( ) );
            else
              printf( "%s", pszOutBuffer );
    
            // Free the memory allocated to the buffer.
            delete [] pszOutBuffer;
          }
        } while( dwSize > 0 );
      }
    
    
      // Report any errors.
      if( !bResults )
        printf( "Error %d has occurred.\n", GetLastError( ) );
    
      // Close any open handles.
      if( hRequest ) WinHttpCloseHandle( hRequest );
      if( hConnect ) WinHttpCloseHandle( hConnect );
      if( hSession ) WinHttpCloseHandle( hSession );
    }
    Last edited by crummy; 02-18-2005 at 07:22 PM. Reason: adding code

  4. #4
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    That VB script errors. Line 4, char 1, error 0x80072EFD. Source (null). Hope that helps...
    Did you replace the "x.x.x.x" in the URL with the correct IP address? Error 0x80072EFD maps to:
    Code:
    ERROR_WINHTTP_CANNOT_CONNECT 
       12029
       Returned if connection to the server failed.
    This suggests that the IP address is wrong or the device is off. I'd try to get the UPnP API working before attempting to use raw HTTP. The UPnP API supports return values and events which may be complicated to implement with the raw HTTP.
    Edit: Here's the code I stole off MSDN for what I beleive is initiating a web request. I can't seem to get it to compile; its made for Microsoft's compiler maybe? I'm swapping between Dev-C++ and LCC. I've already modified a bit of code to prevent a few errors, hopefully I didn't break anything while I did...
    The only chages required were to replace new/delete with malloc/free. This compiles with MSVC or LCC. My version of Dev-C++ seems to be missing the winhttp header.
    Code:
    #include <windows.h>
    #include <winhttp.h>
    #include <stdio.h>
    
    #if defined(_MSC_VER)
    #pragma comment(lib, "Winhttp.lib")
    #elif defined(__LCC__)
    #pragma lib<WinHttp.lib>
    #endif
    
    int main(void) {
      DWORD dwSize = 0;
      DWORD dwDownloaded = 0;
      LPSTR pszOutBuffer;
      BOOL  bResults = FALSE;
      HINTERNET  hSession = NULL,
                 hConnect = NULL,
                 hRequest = NULL;
    
      // Use WinHttpOpen to obtain a session handle.
      hSession = WinHttpOpen( L"WinHTTP Example/1.0",
                              WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                              WINHTTP_NO_PROXY_NAME,
                              WINHTTP_NO_PROXY_BYPASS, 0 );
    
      // Specify an HTTP server.
      if( hSession )
        hConnect = WinHttpConnect( hSession, L"www.microsoft.com",
                                   INTERNET_DEFAULT_HTTPS_PORT, 0 );
    
      // Create an HTTP request handle.
      if( hConnect )
        hRequest = WinHttpOpenRequest( hConnect, L"GET", NULL,
                                       NULL, WINHTTP_NO_REFERER,
                                       WINHTTP_DEFAULT_ACCEPT_TYPES,
                                       WINHTTP_FLAG_SECURE );
    
      // Send a request.
      if( hRequest )
        bResults = WinHttpSendRequest( hRequest,
                                       WINHTTP_NO_ADDITIONAL_HEADERS, 0,
                                       WINHTTP_NO_REQUEST_DATA, 0,
                                       0, 0 );
    
    
      // End the request.
      if( bResults )
        bResults = WinHttpReceiveResponse( hRequest, NULL );
    
      // Keep checking for data until there is nothing left.
      if( bResults )
      {
        do
        {
          // Check for available data.
          dwSize = 0;
          if( !WinHttpQueryDataAvailable( hRequest, &dwSize ) )
            printf( "Error %u in WinHttpQueryDataAvailable.\n",
                    GetLastError( ) );
    
          // Allocate space for the buffer.
          // char pszOutBuffer;
          pszOutBuffer = malloc(dwSize+1);
          if( !pszOutBuffer )
          {
            printf( "Out of memory\n" );
            dwSize=0;
          }
          else
          {
            // Read the data.
            int newsize;
            newsize = dwSize+1;
            ZeroMemory( pszOutBuffer, newsize );
    
            if( !WinHttpReadData( hRequest, (LPVOID)pszOutBuffer,
                                  dwSize, &dwDownloaded ) )
              printf( "Error %u in WinHttpReadData.\n", GetLastError( ) );
            else
              printf( "%s", pszOutBuffer );
    
            // Free the memory allocated to the buffer.
            free(pszOutBuffer);
          }
        } while( dwSize > 0 );
      }
    
    
      // Report any errors.
      if( !bResults )
        printf( "Error %d has occurred.\n", GetLastError( ) );
    
      // Close any open handles.
      if( hRequest ) WinHttpCloseHandle( hRequest );
      if( hConnect ) WinHttpCloseHandle( hConnect );
      if( hSession ) WinHttpCloseHandle( hSession );
    
      return 0;
    }

  5. #5
    Registered User
    Join Date
    Feb 2005
    Posts
    16
    Hehe, the first time I forgot to replace the x.x.x.x's... but no, the address was right. Just forgot the port last time. Anyway, now I get this:
    Script: ...test.vbs
    Line: 6
    Char: 1
    Error: Unspecified error
    Code: 80004005
    Source: (null)
    Do I need to install the API?

    Here's the XML doc - as small as possible so it doesn't take up too much room...:
    Code:
    <root xmlns="urn:schemas-upnp-org:device-1-0">
      <specVersion>
        <major>1</major>
        <minor>0</minor>
      </specVersion>
      <device>
        <deviceType>urn:schemas-upnp-org:device:ZonePlayer:1</deviceType>
        <friendlyName>10.0.1.140 - Sonos ZonePlayer</friendlyName>
    
        <manufacturer>Sonos, Inc.</manufacturer>
        <manufacturerURL>http://www.sonos.com</manufacturerURL>
        <modelDescription>Sonos ZonePlayer</modelDescription>
        <modelName>ZonePlayer</modelName>
        <modelNumber>1</modelNumber>
        <modelURL>http://www.sonos.com/zoneplayer/</modelURL>
    
        <serialNumber>0</serialNumber>
        <softwareVersion>1.0-14041</softwareVersion>
        <UDN>uuid:RINCON_000E5810051601400</UDN>
        <UPC>0</UPC>
        <serviceList>
          <service>
            <serviceType>urn:schemas-upnp-org:service:AudioIn:1</serviceType>
    
            <serviceId>urn:upnp-org:serviceId:AudioIn</serviceId>
            <controlURL>/AudioIn/Control</controlURL>
            <eventSubURL>/AudioIn/Event</eventSubURL>
            <SCPDURL>/xml/AudioIn1.xml</SCPDURL>
          </service>
          <service>
            <serviceType>urn:schemas-upnp-org:service:DeviceProperties:1</serviceType>
    
            <serviceId>urn:upnp-org:serviceId:DeviceProperties</serviceId>
            <controlURL>/DeviceProperties/Control</controlURL>
            <eventSubURL>/DeviceProperties/Event</eventSubURL>
            <SCPDURL>/xml/DeviceProperties1.xml</SCPDURL>
          </service>    
          <service>
            <serviceType>urn:schemas-upnp-org:service:SystemProperties:1</serviceType>
    
            <serviceId>urn:upnp-org:serviceId:SystemProperties</serviceId>
            <controlURL>/SystemProperties/Control</controlURL>
            <eventSubURL>/SystemProperties/Event</eventSubURL>
            <SCPDURL>/xml/SystemProperties1.xml</SCPDURL>
          </service>    
          <service>
            <serviceType>urn:schemas-upnp-org:service:ZoneGroupTopology:1</serviceType>
    
            <serviceId>urn:upnp-org:serviceId:ZoneGroupTopology</serviceId>
            <controlURL>/ZoneGroupTopology/Control</controlURL>
            <eventSubURL>/ZoneGroupTopology/Event</eventSubURL>
            <SCPDURL>/xml/ZoneGroupTopology1.xml</SCPDURL>
          </service>    
          <service>
            <serviceType>urn:schemas-upnp-org:service:GroupManagement:1</serviceType>
    
            <serviceId>urn:upnp-org:serviceId:GroupManagement</serviceId>
            <controlURL>/GroupManagement/Control</controlURL>
            <eventSubURL>/GroupManagement/Event</eventSubURL>
            <SCPDURL>/xml/GroupManagement1.xml</SCPDURL>
          </service>
        </serviceList>
        <deviceList>
    
          <device>
        <deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>
        <friendlyName>10.0.1.140 - Sonos ZonePlayer Media Server</friendlyName>
        <manufacturer>Sonos, Inc.</manufacturer>
        <manufacturerURL>http://www.sonos.com</manufacturerURL>
        <modelDescription>Sonos ZonePlayer Media Server</modelDescription>
    
        <modelName>ZonePlayer Media Server</modelName>
        <modelNumber>1</modelNumber>
        <modelURL>http://www.sonos.com/zoneplayer/</modelURL>
        <serialNumber>0</serialNumber>
        <UDN>uuid:RINCON_000E5810051601400_MS</UDN>
        <UPC>0</UPC>
    
        <serviceList>
          <service>
            <serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>
            <serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId>
            <controlURL>/MediaServer/ContentDirectory/Control</controlURL>
            <eventSubURL>/MediaServer/ContentDirectory/Event</eventSubURL>
            <SCPDURL>/xml/ContentDirectory1.xml</SCPDURL>
    
          </service>
          <service>
            <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
            <serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
            <controlURL>/MediaServer/ConnectionManager/Control</controlURL>
            <eventSubURL>/MediaServer/ConnectionManager/Event</eventSubURL>
            <SCPDURL>/xml/ConnectionManager1.xml</SCPDURL>
    
          </service>
        </serviceList>
          </device>
          <device>
        <deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>
        <friendlyName>10.0.1.140 - Sonos ZonePlayer Media Renderer</friendlyName>
        <manufacturer>Sonos, Inc.</manufacturer>
    
        <manufacturerURL>http://www.sonos.com</manufacturerURL>
        <modelDescription>Sonos ZonePlayer Media Renderer</modelDescription>
        <modelName>ZonePlayer Media Renderer</modelName>
        <modelNumber>1</modelNumber>
        <modelURL>http://www.sonos.com/zoneplayer/</modelURL>
        <serialNumber>0</serialNumber>
    
        <UDN>uuid:RINCON_000E5810051601400_MR</UDN>
        <UPC>0</UPC>
        <serviceList>
          <service>
            <serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>
            <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
            <controlURL>/MediaRenderer/RenderingControl/Control</controlURL>
    
            <eventSubURL>/MediaRenderer/RenderingControl/Event</eventSubURL>
            <SCPDURL>/xml/RenderingControl1.xml</SCPDURL>
          </service>
          <service>
            <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
            <serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
            <controlURL>/MediaRenderer/ConnectionManager/Control</controlURL>
    
            <eventSubURL>/MediaRenderer/ConnectionManager/Event</eventSubURL>
            <SCPDURL>/xml/ConnectionManager1.xml</SCPDURL>
          </service>
          <service>
            <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
            <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
            <controlURL>/MediaRenderer/AVTransport/Control</controlURL>
    
            <eventSubURL>/MediaRenderer/AVTransport/Event</eventSubURL>
            <SCPDURL>/xml/AVTransport1.xml</SCPDURL>
          </service>
        </serviceList>
          </device>
        </deviceList>
      </device>
    </root>


    As for the new code - yay, it compiles! Except on Dev-C++ it crashes.
    AppName: nowplaying.exe AppVer: 0.0.0.0 ModName: unknown ModVer: 0.0.0.0 Offset: 00004168
    Anyway, that doesn't matter too much - it compiles with LCC.

    So now that I can make an HTTP connection, I beleive I can use that to get UPnP information, and change it with GET's (or was it POST?) Is that right?

    Edit: Another question. How do I specify a custom port with WinHttpConnect? I need to use port 1400, not INTERNET_DEFAULT_PORT or INTERNET_DEFAULT_HTTPS_PORT or even INTERNET_DEFAULT_HTTP_PORT. I tried just substituting a 1400 in there but no luck.

    Edit 2: Er. OK. So it does accept just replacing INTERNET_DEFAULT_PORT with 1400. But I still get a 12005 error, which I believe is the equivalent of a 404. My URL is http://10.0.1.136:1400/xml/zone_player.xml and my code is hConnect = WinHttpConnect( hSession, L"http://10.0.1.136/xml/zone_player.xml", 1400, 0 );
    Last edited by crummy; 02-19-2005 at 12:54 PM. Reason: update

  6. #6
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    >> Hehe, the first time I forgot to replace the x.x.x.x's... but no, the address was right. Just forgot the port last time. Anyway, now I get this: <<

    Try changing line 6 from:
    Code:
    Set upDevice = upDescriptionDoc.RootDevice()
    to:
    Code:
    upDevice = upDescriptionDoc.RootDevice
    I think it should work with that change.

    >> As for the new code - yay, it compiles! Except on Dev-C++ it crashes. <<

    The fact that it crashes on Dev-C++ seems to suggest there is a problem with the code, but I can't see any issues.

    >> So now that I can make an HTTP connection, I beleive I can use that to get UPnP information, and change it with GET's (or was it POST?) Is that right? <<

    I'm not sure of the details of the UPnP protocol so I don't know. I'm not sure how getting the xml file will help you.

    >> But I still get a 12005 error, which I believe is the equivalent of a 404. <<

    WinHttpConnect takes the server name. WinHttpOpenRequest takes the file name. You can not pass the complete URL to WinHttpConnect. This is why you are getting ERROR_WINHTTP_INVALID_URL.

    You can make the following changes.
    Code:
      // Specify an HTTP server.
      if( hSession )
        hConnect = WinHttpConnect( hSession, L"10.0.1.136",
                                   1400, 0 );
    
      // Create an HTTP request handle.
      if( hConnect )
        hRequest = WinHttpOpenRequest( hConnect, L"GET", L"/xml/zone_player.xml",
                                       NULL, WINHTTP_NO_REFERER,
                                       WINHTTP_DEFAULT_ACCEPT_TYPES,
                                       0);
    I'll post back with more information based on the XML file you posted later.

  7. #7
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    Ok, start the device playing and run this vcscript. If it works, it should pause the device.
    Code:
    Dim upDescriptionDoc, upDevice, upService, upMediaRenderer
    
    Set upDescriptionDoc = CreateObject("UPnP.DescriptionDocument")
    upDescriptionDoc.Load("http://10.0.1.136:1400/xml/zone_player.xml")
    
    upDevice = upDescriptionDoc.RootDevice
    
    Set upMediaRenderer = upDevice.Children("uuid:RINCON_000E5810051601400_MR")
    ''' upMediaRenderer = upDevice.Children("uuid:RINCON_000E5810051601400_MR")
    
    Set upService = upMediaRenderer.Services("urn:schemas-upnp-org:service:AVTransport:1")
    ''' upService = upMediaRenderer.Services("urn:schemas-upnp-org:service:AVTransport:1")
    
    
    Dim outArgs(0)
    Dim inArgs(1)
    
    inArgs(0) = 0
    
    upService.InvokeAction "Pause", inArgs, outArgs
    '' upService.InvokeAction "Next", inArgs, outArgs
    '' upService.InvokeAction "Stop", inArgs, outArgs

  8. #8
    Registered User
    Join Date
    Feb 2005
    Posts
    16


    Same error code as before for the VBS file.

    However, the new C code works fine. I can now get the XML document. Which, though I suspect may not be too useful, is, I think, a large step in the right direction. I hope.

    Edit: Here's a quote from someone working on something similar. "UPNP calls are just POST calls sent to the proper URL on the device" How do I attach a POST call to my program? WinHttpRequest?
    Last edited by crummy; 02-21-2005 at 01:09 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Please Help - C code creates dynamic HTML
    By Christie2008 in forum C Programming
    Replies: 19
    Last Post: 04-02-2008, 07:36 PM
  2. Parsing HTML files
    By slcjoey in forum C++ Programming
    Replies: 2
    Last Post: 08-28-2005, 07:01 AM
  3. Stacks, classes, HTML tags, and parsing.
    By Shinobi-wan in forum C++ Programming
    Replies: 5
    Last Post: 10-01-2003, 05:50 PM
  4. Design + HTML
    By orbitz in forum C Programming
    Replies: 8
    Last Post: 11-21-2002, 06:32 AM
  5. I hate string parsing with a passion
    By DavidP in forum A Brief History of Cprogramming.com
    Replies: 2
    Last Post: 03-19-2002, 07:30 PM