librets: A C++ RETS Library

1.5.1

librets is a C++ RETS library. It provides support for logging in, logging out, searching, and metadata retrieval. The main interface is through the librets::RetsSession class.

Here is an example demonstrating searching:

#include "librets.h"
#include <iostream>

using namespace librets;
using std::cout;
using std::cerr;
using std::endl;

int main(int argc, char * argv[])
{
    try
    {
        RetsSessionPtr session(
            new RetsSession("http://demo.crt.realtors.org:6103/rets/login"));
        session->Login("Joe", "Schmoe");
        
        if (session->GetDetectedRetsVersion() != session->GetRetsVersion())
        {
            cout << "** Warning, requested RETS version \"" 
                 << session->RetsVersionToString(session->GetRetsVersion())
                 << "\", got version \""
                 << session->RetsVersionToString(session->GetDetectedRetsVersion())
                 << "\" ** " << endl;
        }

        SearchRequestAPtr searchRequest(
            new SearchRequest("Property", "RES", 
                              "(ListPrice=300000-)"));
        
        SearchResultSetAPtr results = session->Search(searchRequest.get());
        while (results->HasNext())
        {
            cout << "ListingID: " << results->GetString("ListingID") << endl;
            cout << "ListPrice: " << results->GetString("ListPrice") << endl;
            cout << " Bedrooms: " << results->GetString("Bedrooms") << endl;
            cout << "     City: " << results->GetString("City") << endl;
            cout << endl;
        }
        
        session->Logout();
    }
    catch (RetsException & e)
    {
        e.PrintFullReport(cerr);
    }
}

Here is an example showing how to use metadata:

/*
 * Copyright (C) 2005-2009 National Association of REALTORS(R)
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished
 * to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that
 * both the above copyright notice(s) and this permission notice
 * appear in supporting documentation.
 */

#include "librets.h"
#include "Options.h"
#include <iostream>

using namespace librets;
using std::string;
using std::vector;
using std::cout;
using std::endl;
using std::exception;

void dumpSystem(RetsMetadata * metadata);
void dumpAllForeignKeys(RetsMetadata * metadata);
void dumpAllResources(RetsMetadata * metadata);
void dumpAllClasses(RetsMetadata * metadata, MetadataResource * resource);
void dumpAllTables(RetsMetadata * metadata, MetadataClass * aClass);
void dumpAllLookups(RetsMetadata * metadata, MetadataResource * resource);
void dumpAllLookupTypes(RetsMetadata * metadata, MetadataLookup * lookup);

int main(int argc, char * argv[])
{
    try
    {
        Options options;
        if (!options.ParseCommandLine(argc, argv))
        {
            return 0;
        }

        RetsSessionPtr session = options.RetsLogin();
        if (!session)
        {
            cout << "Login failed\n";
            return -1;
        }

        if (session->GetDetectedRetsVersion() != session->GetRetsVersion())
        {
            cout << "** Warning, requested RETS version \"" 
                 << session->RetsVersionToString(session->GetRetsVersion())
                 << "\", got version \""
                 << session->RetsVersionToString(session->GetDetectedRetsVersion())
                 << "\" ** " << endl;
        }
        
        if (session->GetDetectedRetsVersion() == RETS_1_7)
        {
            try
            {
                ServerInformationResponseAPtr serverInfo = session->GetServerInformation();
                
                if (serverInfo.get())
                {
                    StringVector parameters = serverInfo->GetParameters();
                    StringVector::const_iterator i;
                    for (i = parameters.begin(); i != parameters.end(); i++)
                    {
                        if (i->empty())
                        {
                            continue;
                        }
                        cout << *i << ": " << serverInfo->GetValue(*i) << endl;
                    }
                }
            }
            catch (RetsException & e)
            {
               /*
                * The ServerInformation Transaction is not supported.
                * Continue silently.
                */
            }
        }

        RetsMetadata * metadata = session->GetMetadata();
        dumpSystem(metadata);
        dumpAllForeignKeys(metadata);
        dumpAllResources(metadata);

        session->Logout();
    }
    catch (RetsException & e)
    {
        e.PrintFullReport(cout);
        return 1;
    }
    catch (exception & e)
    {
        cout << e.what() << endl;
        return 2;
    }
    return 0;
}

void dumpSystem(RetsMetadata * metadata)
{
    MetadataSystem * system = metadata->GetSystem();
    cout << "System ID: " << system->GetSystemID() << endl;
    cout << "System Description: " << system->GetSystemDescription() << endl;
    cout << "Comments: " << system->GetComments() << endl;
}

void dumpAllForeignKeys(RetsMetadata * metadata)
{
    MetadataForeignKeyList foreign_keys = metadata->GetAllForeignKeys();
    MetadataForeignKeyList::iterator i;
    cout << endl;
    for (i = foreign_keys.begin(); i != foreign_keys.end(); i++)
    {
        MetadataForeignKey * foreign_key = *i;
        
        cout << "Foreign Key ID:" << foreign_key->GetForeignKeyID() << endl;
        cout << "  Parent Resource: " << foreign_key->GetParentResourceID();
        cout << ", Class: " << foreign_key->GetParentClassID();
        cout << ", Name: " << foreign_key->GetParentSystemName() << endl;
        cout << "  Child Resource: " << foreign_key->GetChildResourceID();
        cout << ", Class: " << foreign_key->GetChildClassID();
        cout << ", Name: " << foreign_key->GetChildSystemName() << endl;
    }
}

void dumpAllResources(RetsMetadata * metadata)
{
    MetadataResourceList resources = metadata->GetAllResources();
    MetadataResourceList::iterator i;
    cout << endl;
    for (i = resources.begin(); i != resources.end(); i++)
    {
        MetadataResource * resource = *i;
        dumpAllClasses(metadata, resource);
    }

    for (i = resources.begin(); i != resources.end(); i++)
    {
        MetadataResource * resource = *i;
        dumpAllLookups(metadata, resource);
    }
}



void dumpAllClasses(RetsMetadata * metadata, MetadataResource * resource)
{
    string resourceName = resource->GetResourceID();
    
    MetadataClassList classes =
        metadata->GetAllClasses(resourceName);
    MetadataClassList::iterator i;
    for (i = classes.begin(); i != classes.end(); i++)
    {
        MetadataClass * aClass = *i;
        cout << "Resource name: " << resourceName << " ["
             << resource->GetStandardName() << "]" << endl;
        cout << "Class name: " << aClass->GetClassName() << " ["
             << aClass->GetStandardName() << "]" << endl;
        dumpAllTables(metadata, aClass);
        cout << endl;
    }
}

void dumpAllTables(RetsMetadata * metadata, MetadataClass * aClass)
{
    MetadataTableList tables = metadata->GetAllTables(aClass);
    MetadataTableList::iterator i;
    for (i = tables.begin(); i != tables.end(); i++)
    {
        MetadataTable * table = *i;
        cout << "Table name: " << table->GetSystemName() << " ["
             << table->GetStandardName() << "]" << " ("
             << table->GetDataType() << ")";
        if (!table->GetMetadataEntryID().empty())
        {
            cout << " MetadataEntryID: " << table->GetMetadataEntryID();
        }
        if (table->InKeyIndex())
        {
            cout << " InKeyIndex";
        }
        cout << endl;
    }
}

void dumpAllLookups(RetsMetadata * metadata, MetadataResource * resource)
{
    string resourceName = resource->GetResourceID();
    
    MetadataLookupList classes =
        metadata->GetAllLookups(resourceName);
    MetadataLookupList::iterator i;
    for (i = classes.begin(); i != classes.end(); i++)
    {
        MetadataLookup * lookup = *i;
        cout << "Resource name: " << resourceName << " ["
             << resource->GetStandardName() << "]" << endl;
        cout << "Lookup name: " << lookup->GetLookupName() << " ("
             << lookup->GetVisibleName() << ")";

        if (!lookup->GetMetadataEntryID().empty())
        {
            cout << " MetadataEntryID: " << lookup->GetMetadataEntryID();
        }
            
        cout << endl;
        dumpAllLookupTypes(metadata, lookup);
        cout << endl;
    }
}

void dumpAllLookupTypes(RetsMetadata * metadata, MetadataLookup * lookup)
{
    MetadataLookupTypeList lookupTypes = metadata->GetAllLookupTypes(lookup);
    MetadataLookupTypeList::const_iterator i ;
    for (i = lookupTypes.begin(); i != lookupTypes.end(); i++)
    {
        MetadataLookupType * lookupType = *i;
        cout << "Lookup value: " << lookupType->GetValue() << " ("
             << lookupType->GetShortValue() << ", "
             << lookupType->GetLongValue() << ")"; 

        if (!lookupType->GetMetadataEntryID().empty())
        {
            cout << " MetadataEntryID: " << lookupType->GetMetadataEntryID();
        }
       
        cout << endl;
    }
}

Lastly, here is an example showing more sophisticated usage. In this example, metadata is searched to determine the key field for the given resource and class. If this is a RETS 1.7 and later server, then the timestamp field is also determined from metadata. If pre RETS 1.7, then the user must provide the name of the timetsamp field. By default, and using the demo server, this example will search residential property listings.

Using the keyfield, all listings are returned that have been changed since the specified timestamp. The first search only returns the keyfield. The reason for this is that many servers limit the amount of data to be returned at any one time. If more columns are returned, less total listings can be returned. The intent is that given only the keyfield, we should be able to retrieve all the keys since the date in the timestamp.

Next, using the keyfield, the full listing is fetched and returned. Then all of the Photos for that listing are retrieved and their names displayed.

/*
 * Copyright (C) 2008 National Association of REALTORS(R)
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished
 * to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that
 * both the above copyright notice(s) and this permission notice
 * appear in supporting documentation.
 */

#include "librets.h"
#include "Options.h"
#include <iostream>
#include <iomanip>
#include <boost/date_time.hpp>
#include "librets/str_stream.h"

using namespace librets;
using std::cout;
using std::cerr;
using std::endl;
using std::string;
using std::stringstream;
using std::setw;
using std::vector;
namespace po = boost::program_options;

int main(int argc, char * argv[])
{
    try
    {
        string                      classTimeStamp;
        SearchRequest::CountType    count;
        string                      countString;
        int                         defaultLimit = SearchRequest::LIMIT_DEFAULT;
        int                         defaultOffset = SearchRequest::OFFSET_NONE;
        SearchRequest::FormatType   format = SearchRequest::COMPACT_DECODED;
        string                      keyField;
        string                      lastModified;
        vector<string>              listingIds;
        int                         limit;
        RetsMetadata *              metadata;
        MetadataClass *             metadataClass;
        MetadataResource *          metadataResource;
        int                         offset;
        bool                        printCount;
        string                      query;
        string                      resource;
        string                      searchClass;
        SearchRequestAPtr           searchRequest;
        string                      select;
        bool                        standardNames = true;
        int                         totalListings = 0;
        string                      type;

        Options options;
        options.descriptions.add_options()
            ("resource,r", po::value<string>(&resource)
             ->default_value("Property"), "Search resource")
            ("class,C", po::value<string>(&searchClass)
             ->default_value("RES"), "Search class")
            ("timestamp,T", po::value<string>(&classTimeStamp)
             ->default_value(""), "Systemname of the Class TimeStamp field")
             ("lastmodified,L", po::value<string>(&lastModified)
              ->default_value(""), "RETS timestamp of the earliest date from which to select")
            ("select,s", po::value<string>(&select)
             ->default_value("ListingID,ListPrice,Beds,City"), "Search select")
            ("query,q", po::value<string>(&query)
             ->default_value("(ListPrice=300000-)"), "Search query")
            ("system-names,S", "Use system names, instead of standard names")
            ("type,t", po::value<string>(&type)
             ->default_value("Photo"), "Media Type")
            ("compact", "Use COMPACT instead of COMPACT-DECODED")
            ("limit,L", po::value<int>(&limit)
             ->default_value(defaultLimit), "Set the limit")
            ("offset,O", po::value<int>(&offset)
             ->default_value(defaultOffset), "Set the offset")
            ("count,n", po::value<string>(&countString)
             ->default_value("yes"),
             "Set the count type: no, yes or count-only)")
            ;
        if (!options.ParseCommandLine(argc, argv))
        {
            return 0;
        }
        if (options.count("system-names"))
        {
            standardNames = false;
        }
        if (options.count("compact"))
        {
            format = SearchRequest::COMPACT;
        }
        
        if (countString == "yes")
        {
            count = SearchRequest::RECORD_COUNT_AND_RESULTS;
            printCount = true;
        }
        else if (countString == "no")
        {
            count = SearchRequest::NO_RECORD_COUNT;
            printCount = false;
        }
        else if (countString == "count-only")
        {
            count = SearchRequest::RECORD_COUNT_ONLY;
            printCount = true;
        }
        else
        {
            count = SearchRequest::RECORD_COUNT_AND_RESULTS;
            printCount = true;
        }

        RetsSessionPtr session = options.RetsLogin();
        if (!session)
        {
            cout << "Login failed\n";
            return -1;
        }
        
        /*
         * Find the keyfield for the resource.
         */
        metadata = session->GetMetadata();
        metadataResource = metadata->GetResource(resource);
        if (metadataResource == NULL)
        {
            cout << "Invalid resource: " << resource << std::endl;
            session->Logout();
            return -1;
        }
        keyField = metadataResource->GetKeyField();
        
        /*
         * Find the timetsamp field if it is known (RETS 1.7 and later). If 
         * not known, the user must provide it.
         */
        metadataClass = metadata->GetClass(resource, searchClass);
        if (metadataClass == NULL)
        {
            cout << "Invalid resource:class: "
                 << resource
                 << ":"
                 << searchClass 
                 << std::endl;
            session->Logout();
            return -1;
        }
        if (classTimeStamp.length() == 0)
            classTimeStamp = metadataClass->GetStringAttribute("ClassTimeStamp");
            
        if (classTimeStamp.length() == 0)
        {
            cout << "Class "
                 << resource
                 << ":"
                 << searchClass
                 << " has no ClassTimeStamp specified in the metadata. "
                 << std::endl
                 << "Please manually provide one using the --timestamp switch."
                 << std::endl;
            session->Logout();
            return -1;
        }
        
        /*
         * See if the last modified timestamp has been provided. If not, take the
         * current time less 24 hours.
         */
        if (lastModified.length() == 0)
        {
            stringstream ss;
            boost::gregorian::date_facet *output_facet = new boost::gregorian::date_facet("%Y-%m-%d");
            ss.imbue(std::locale(std::locale::classic(), output_facet));
            boost::gregorian::date d(boost::gregorian::day_clock::local_day());
            d -= boost::gregorian::days(1);
            ss << d;
            lastModified = ss.str();
        }
        
        /*
         * OK - let's find all listings that have changed since the lastModified date
         */

        /*
         * Construct the query.
         */
        searchRequest = session->CreateSearchRequest(
                                    resource, searchClass,
                                    str_stream()    << "("
                                                    << classTimeStamp
                                                    << "="
                                                    << lastModified
                                                    << "+)");

        searchRequest->SetSelect(keyField);
        /*
         * Must use system names for this search.
         */
        searchRequest->SetStandardNames(false);
        searchRequest->SetLimit(SearchRequest::LIMIT_DEFAULT);
        searchRequest->SetOffset(SearchRequest::OFFSET_NONE);
        searchRequest->SetCountType(SearchRequest::RECORD_COUNT_AND_RESULTS);
        searchRequest->SetFormatType(SearchRequest::COMPACT);
        
        SearchResultSetAPtr results = session->Search(searchRequest.get());
        if (printCount)
        {
            cout << "Matching record count: " << results->GetCount() << endl;
        }
        /*
         * For all listings found, fetch the full listing detail and then the 
         * associated Photos.
         */
        while (results->HasNext())
        {
            totalListings++;
            listingIds.push_back(results->GetString(keyField));
            /*
             * Create a new search to fetch all the detail for the listing.
             */   
            SearchRequestAPtr   listingRequest = session->CreateSearchRequest(
                                                            resource,
                                                            searchClass,
                                                            str_stream()    << "("
                                                                            << keyField
                                                                            << "="
                                                                            << results->GetString(keyField)
                                                                            << ")");
            listingRequest->SetStandardNames(false);
            listingRequest->SetLimit(SearchRequest::LIMIT_DEFAULT);
            listingRequest->SetOffset(SearchRequest::OFFSET_NONE);
            listingRequest->SetCountType(SearchRequest::NO_RECORD_COUNT);
            listingRequest->SetFormatType(SearchRequest::COMPACT);
            
            SearchResultSetAPtr listingResult = session->Search(listingRequest.get());
            StringVector columns = listingResult->GetColumns();
            
            while (listingResult->HasNext())
            {
                /*
                 * Show the listing detail.
                 */
                for (StringVector::iterator i = columns.begin(); i != columns.end(); i++)
                {
                    string column = *i;
                    cout << setw(15) << column << ": "
                        << setw(0) << listingResult->GetString(column) << endl;
                }
            }
            
            /*
             * Now set up to fetch the objects associated with this listing.
             */
            GetObjectRequest getObjectRequest(resource, type);
            getObjectRequest.AddAllObjects(results->GetString(keyField));
            
            GetObjectResponseAPtr   getObjectResponse = session->GetObject(&getObjectRequest);
            
            ObjectDescriptor * objectDescriptor;
            while ((objectDescriptor = getObjectResponse->NextObject()))
            {
                /*
                 * Report the object details.
                 */
                string  objectKey   = objectDescriptor->GetObjectKey();
                int     objectId    = objectDescriptor->GetObjectId();
                string  contentType = objectDescriptor->GetContentType();
                string  description = objectDescriptor->GetDescription();
                int     replyCode   = objectDescriptor->GetRetsReplyCode();
                string  replyText   = objectDescriptor->GetRetsReplyText();
                
                cout << "Object "
                     << objectKey
                     << ":"
                     << objectId;
                if (!description.empty())
                    cout << ", description: "
                         << description;
                cout << endl;
                
                if (replyCode != 0)
                    cout << "***: "
                         << replyCode
                         << ": "
                         << replyText
                         << endl;
            }
            cout << endl;
        }
        cout << "Total Listings Retrieved: " << totalListings << endl;
        cout << "Listing IDs:" << endl;
        
        for (vector<string>::iterator i = listingIds.begin(); i != listingIds.end(); i++)
            cout << *i << endl;
        
        session->Logout();
    }
    catch (RetsException & e)
    {
        e.PrintFullReport(cerr);
    }
    catch (std::exception & e)
    {
        cerr << e.what() << endl;
    }
}

Here is the same example using the .NET language C#:

using System;
using System.Collections;
using System.IO;
using System.Collections.Specialized;
using librets;

public class Interleaved
{
    /*
     * This class demonstrates the interleaving of search transactions.
     */
    static void Main(string[] args)
    {
        Options options  = new Options();

        if (!options.Parse(args))
            Environment.Exit(1);

        RetsSession session = options.SessionFactory();

        try {

            if (!session.Login(options.user_name, options.user_password))
            {
                Console.WriteLine("Invalid login");
                Environment.Exit(1);
            }
        } catch (Exception e)
        {
            Console.WriteLine("RetsException: " + e);
            Environment.Exit(1);
        }

        RetsVersion version = session.GetDetectedRetsVersion();
        Console.WriteLine("RETS Version: " +
            ((version == RetsVersion.RETS_1_5) ? "1.5" : 
            ((version == RetsVersion.RETS_1_7) ? "1.7" : "1.0")));

        /*
         * Find the key field for the resource.
         */
        RetsMetadata metadata = session.GetMetadata();
        MetadataResource metadataResource = metadata.GetResource(options.search_type);

        if (metadataResource == null)
        {
            Console.WriteLine("Invalid resource: " + options.search_type);
            session.Logout();
            Environment.Exit(1);
        }

        string keyField = metadataResource.GetKeyField();

        /*
         * Find the timestamp field if it is known (RETS 1.7 and later). If
         * not known, then the user must provide it.
         */
        MetadataClass metadataClass = metadata.GetClass(options.search_type, options.search_class);
        if (metadataClass == null)
        {
            Console.WriteLine("Invalid resource:class: " + options.search_type + ":" + options.search_class);
            session.Logout();
            Environment.Exit(2);
        }

        if (options.classTimeStamp != null && options.classTimeStamp.Length == 0)
            options.classTimeStamp = metadataClass.GetStringAttribute("ClassTimeStamp");

        if (options.classTimeStamp == null || options.classTimeStamp.Length == 0)
        {
            Console.WriteLine("Class " + options.search_type +
                                  ":" + options.search_class +
                              " has no ClassTimeStamp specified in the metadata.");
            Console.WriteLine("Please manually provide one using the --timetsamp switch.");
            session.Logout();
            Environment.Exit(2);
        }
        
        /*
         * See if the last modified timestamp has been provided. If not, use yesterday.
         */
        if (options.lastModified == null || options.lastModified.Length == 0)
        {
            DateTime ts = DateTime.Now;

            options.lastModified = ts.AddDays(-1).ToString("yyyy-MM-dd");
        }

        /*
         * OK - let's find all listings that have changed since the lastModified date.
         */
        
        SearchRequest searchRequest = session.CreateSearchRequest(
                                                options.search_type, 
                                                options.search_class,
                                                "(" + 
                                                options.classTimeStamp.ToString() + 
                                                "=" +
                                                options.lastModified.ToString() +
                                                "+)");

        searchRequest.SetSelect(keyField);
        searchRequest.SetLimit(SearchRequest.LIMIT_NONE);
        searchRequest.SetOffset(SearchRequest.OFFSET_NONE);
        searchRequest.SetCountType(SearchRequest.CountType.RECORD_COUNT_AND_RESULTS);
        searchRequest.SetStandardNames(false);

        /*
         * This starts the outer search. 
         */
        SearchResultSet results = session.Search(searchRequest);
        
        Console.WriteLine("Record count: " + results.GetCount());
        Console.WriteLine();

        while (results.HasNext())
        {
            /*
             * Fetch the listing detail and media. This will cause a separate search transaction
             * to be open within the outer search transaction.
             */
            SearchRequest listingRequest = session.CreateSearchRequest(
                                                            options.search_type,
                                                            options.search_class,
                                                            "(" +
                                                            keyField +
                                                            "=" +
                                                            results.GetString(keyField) +
                                                            ")");
            listingRequest.SetStandardNames(false);
            listingRequest.SetLimit(SearchRequest.LIMIT_DEFAULT);
            listingRequest.SetOffset(SearchRequest.OFFSET_NONE);
            listingRequest.SetCountType(SearchRequest.CountType.NO_RECORD_COUNT);
            listingRequest.SetFormatType(SearchRequest.FormatType.COMPACT);
            
            SearchResultSet listingResult = session.Search(listingRequest);
            IEnumerable  columns = listingResult.GetColumns();
            
            while (listingResult.HasNext())
            {
                /*
                 * Show the listing detail.
                 */
                foreach (string column in columns)
                {
                    Console.WriteLine("{0,15}: {1}", column, listingResult.GetString(column));
                }
                Console.WriteLine();
            
                /*
                 * Now set up to fetch the objects associated with this listing.
                 */
                GetObjectRequest getObjectRequest = new GetObjectRequest(options.search_type, "Photo");
                getObjectRequest.AddAllObjects(listingResult.GetString(keyField));
                
                GetObjectResponse getObjectResponse = session.GetObject(getObjectRequest);
                
                foreach(ObjectDescriptor objectDescriptor in getObjectResponse)
                {
                    /*
                     * Report the object details.
                     */
                    string  objectKey   = objectDescriptor.GetObjectKey();
                    int     objectId    = objectDescriptor.GetObjectId();
                    //string  contentType = objectDescriptor.GetContentType();
                    string  description = objectDescriptor.GetDescription();
                    
                    Console.Write ("Object " + objectKey + ":" + objectId.ToString());
                    if (description.Length > 0)
                        Console.Write (", description: " + description);
                    Console.WriteLine();
                }
                Console.WriteLine("=================");
            }
        }

        session.Logout();
    }

    
}

Generated by  doxygen