int
Dictionary::get_document_index (ustring dictionaryname)
{
    DocMap::iterator dm = m_document_map.find(dictionaryname);
    int dindex;
    if (dm == m_document_map.end()) {
        dindex = m_documents.size();
        m_document_map[dictionaryname] = dindex;
        pugi::xml_document *doc = new pugi::xml_document;
        m_documents.push_back (doc);
        pugi::xml_parse_result parse_result;
        if (boost::ends_with (dictionaryname.string(), ".xml")) {
            // xml file -- read it
            parse_result = doc->load_file (dictionaryname.c_str());
        } else {
            // load xml directly from the string
            parse_result = doc->load_buffer (dictionaryname.c_str(),
                                             dictionaryname.length());
        }
        if (! parse_result) {
            m_context->error ("XML parsed with errors: %s, at offset %d",
                              parse_result.description(),
                              parse_result.offset);
            m_document_map[dictionaryname] = -1;
            return -1;
        }
    } else {
        dindex = dm->second;
    }

    DASSERT (dindex < (int)m_documents.size());
    return dindex;
}
bool
RendererServices::texture (ustring filename, TextureHandle *texture_handle,
                           TexturePerthread *texture_thread_info,
                           TextureOpt &options, ShaderGlobals *sg,
                           float s, float t, float dsdx, float dtdx,
                           float dsdy, float dtdy, int nchannels,
                           float *result, float *dresultds, float *dresultdt)
{
    ShadingContext *context = sg->context;
    if (! texture_thread_info)
        texture_thread_info = context->texture_thread_info();
    bool status;
    if (texture_handle)
        status = texturesys()->texture (texture_handle, texture_thread_info,
                                        options, s, t, dsdx, dtdx, dsdy, dtdy,
                                        nchannels, result, dresultds, dresultdt);
    else
        status = texturesys()->texture (filename,
                                        options, s, t, dsdx, dtdx, dsdy, dtdy,
                                        nchannels, result, dresultds, dresultdt);
    if (!status) {
        std::string err = texturesys()->geterror();
        if (err.size() && sg) {
            context->error ("[RendererServices::texture] %s", err);
        }
    }
    return status;
}
int
Dictionary::dict_find (ustring dictionaryname, ustring query)
{
    int dindex = get_document_index (dictionaryname);
    if (dindex < 0)
        return dindex;
    ASSERT (dindex >= 0 && dindex < (int)m_documents.size());

    Query q (dindex, 0, query);
    QueryMap::iterator qfound = m_cache.find (q);
    if (qfound != m_cache.end()) {
        return qfound->second.valueoffset;
    }

    pugi::xml_document *doc = m_documents[dindex];

    // Query was not found.  Do the expensive lookup and cache it
    pugi::xpath_node_set matches;

    try {
        matches = doc->select_nodes (query.c_str());
    }
    catch (const pugi::xpath_exception& e) {
        m_context->error ("Invalid dict_find query '%s': %s",
                          query.c_str(), e.what());
        return 0;
    }

    if (matches.empty()) {
        m_cache[q] = QueryResult (false);  // mark invalid
        return 0;   // Not found
    }
    int firstmatch = (int) m_nodes.size();
    int last = -1;
    for (int i = 0, e = (int)matches.size(); i < e;  ++i) {
        m_nodes.push_back (Node (dindex, matches[i].node()));
        int nodeid = (int) m_nodes.size()-1;
        if (last < 0) {
            // If this is the first match, add a cache entry for it
            m_cache[q] = QueryResult (true /* it's a node */, nodeid);
        } else {
            // If this is a subsequent match, set the last match's 'next'
            m_nodes[last].next = nodeid;
        }
        last = nodeid;
    }
    return firstmatch;
}
int
Dictionary::dict_find (int nodeID, ustring query)
{
    if (nodeID <= 0 || nodeID >= (int)m_nodes.size())
        return 0;     // invalid node ID

    const Dictionary::Node &node (m_nodes[nodeID]);
    Query q (node.document, nodeID, query);
    QueryMap::iterator qfound = m_cache.find (q);
    if (qfound != m_cache.end()) {
        return qfound->second.valueoffset;
    }

    // Query was not found.  Do the expensive lookup and cache it
    pugi::xpath_node_set matches;
    try {
        matches = node.node.select_nodes (query.c_str());
    }
    catch (const pugi::xpath_exception& e) {
        m_context->error ("Invalid dict_find query '%s': %s",
                          query.c_str(), e.what());
        return 0;
    }

    if (matches.empty()) {
        m_cache[q] = QueryResult (false);  // mark invalid
        return 0;   // Not found
    }
    int firstmatch = (int) m_nodes.size();
    int last = -1;
    for (int i = 0, e = (int)matches.size(); i < e;  ++i) {
        m_nodes.push_back (Node (node.document, matches[i].node()));
        int nodeid = (int) m_nodes.size()-1;
        if (last < 0) {
            // If this is the first match, add a cache entry for it
            m_cache[q] = QueryResult (true /* it's a node */, nodeid);
        } else {
            // If this is a subsequent match, set the last match's 'next'
            m_nodes[last].next = nodeid;
        }
        last = nodeid;
    }
    return firstmatch;
}