/////////////////////////////////////////////////////////////////////////////
// Name:        src/xrc/xmlres.cpp
// Purpose:     XRC resources
// Author:      Vaclav Slavik
// Created:     2000/03/05
// Copyright:   (c) 2000 Vaclav Slavik
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"


#if wxUSE_XRC

#include "wx/xrc/xmlres.h"

#ifndef WX_PRECOMP
    #include "wx/intl.h"
    #include "wx/log.h"
    #include "wx/panel.h"
    #include "wx/frame.h"
    #include "wx/dialog.h"
    #include "wx/settings.h"
    #include "wx/bitmap.h"
    #include "wx/image.h"
    #include "wx/module.h"
    #include "wx/wxcrtvararg.h"
#endif

#include "wx/vector.h"
#include "wx/wfstream.h"
#include "wx/filesys.h"
#include "wx/filename.h"
#include "wx/tokenzr.h"
#include "wx/fontenum.h"
#include "wx/fontmap.h"
#include "wx/artprov.h"
#include "wx/imaglist.h"
#include "wx/dir.h"
#include "wx/xml/xml.h"
#include "wx/config.h"
#include "wx/platinfo.h"

#include <limits.h>
#include <locale.h>

#include <memory>
#include <unordered_set>
#include <vector>

namespace
{

// Helper function to get modification time of either a wxFileSystem URI or
// just a normal file name, depending on the build.
#if wxUSE_DATETIME

wxDateTime GetXRCFileModTime(const wxString& filename)
{
#if wxUSE_FILESYSTEM
    wxFileSystem fsys;
    std::unique_ptr<wxFSFile> file(fsys.OpenFile(filename));

    return file ? file->GetModificationTime() : wxDateTime();
#else // wxUSE_FILESYSTEM
    return wxDateTime(wxFileModificationTime(filename));
#endif // wxUSE_FILESYSTEM
}

#endif // wxUSE_DATETIME

} // anonymous namespace

// Assign the given value to the specified entry or add a new value with this
// name.
static void XRCID_Assign(const wxString& str_id, int value);

// Flags indicating whether the XRC contents was loaded from file or URL, more
// generally: this is usually true but is not set for the records created
// directly from wxXmlDocument.
namespace XRCWhence
{

enum
{
    From_URL = 0,
    From_Doc = 1
};

} // namespace // XRCWhence

class wxXmlResourceDataRecord
{
public:
    // Ctor takes ownership of the document pointer.
    wxXmlResourceDataRecord(const wxString& File_,
                            wxXmlDocument *Doc_,
                            int flags = XRCWhence::From_URL
                           )
        : File(File_), Doc(Doc_)
    {
#if wxUSE_DATETIME
        switch ( flags )
        {
            case XRCWhence::From_URL:
                Time = GetXRCFileModTime(File);
                break;

            case XRCWhence::From_Doc:
                // Leave Time invalid.
                break;
        }
#else
        wxUnusedVar(flags);
#endif
    }

    // Make these objects movable because they can't be copied due to the
    // presence of a unique_ptr member.
    wxXmlResourceDataRecord(const wxXmlResourceDataRecord&) = delete;
    wxXmlResourceDataRecord& operator=(const wxXmlResourceDataRecord&) = delete;

    wxXmlResourceDataRecord(wxXmlResourceDataRecord&&) = default;
    wxXmlResourceDataRecord& operator=(wxXmlResourceDataRecord&&) = default;

    ~wxXmlResourceDataRecord() = default;

    wxString File;
    std::unique_ptr<wxXmlDocument> Doc;
#if wxUSE_DATETIME
    wxDateTime Time;
#endif
};

class wxXmlResourceDataRecords : public std::vector<wxXmlResourceDataRecord>
{
    // this is a class so that it can be forward-declared
};

class wxXmlResourceInternal
{
public:
    std::vector<std::unique_ptr<wxXmlResourceHandler>> m_handlers;
    wxXmlResourceDataRecords m_data;

    // Enabled features.
    std::unordered_set<wxString> m_features;

    static std::vector<std::unique_ptr<wxXmlSubclassFactory>> ms_subclassFactories;
};

class wxIdRange // Holds data for a particular rangename
{
public:
    wxIdRange(const wxXmlNode* node,
              const wxString& rname,
              const wxString& startno,
              const wxString& rsize);

    // Make these objects movable as it should be more efficient to move
    // m_indices than copy them.
    wxIdRange(const wxIdRange&) = delete;
    wxIdRange& operator=(const wxIdRange&) = delete;

    wxIdRange(wxIdRange&&) = default;
    wxIdRange& operator=(wxIdRange&&) = default;

    // Note the existence of an item within the range
    void NoteItem(const wxXmlNode* node, const wxString& item);

    // The manager is telling us that it's finished adding items
    void Finalise(const wxXmlNode* node);

    wxString GetName() const { return m_name; }
    bool IsFinalised() const { return m_finalised; }

private:
    wxString m_name;
    int m_start;
    int m_end;
    unsigned int m_size;
    bool m_item_end_found;
    bool m_finalised;
    std::unordered_set<int> m_indices;
};

class wxIdRangeManager
{
public:
    ~wxIdRangeManager() = default;
    // Gets the global resources object or creates one if none exists.
    static wxIdRangeManager *Get();

    // Sets the global resources object and returns a pointer to the previous
    // one (may be null).
    static wxIdRangeManager *Set(wxIdRangeManager *res);

    // Create a new IDrange from this node
    void AddRange(const wxXmlNode* node);
    // Tell the IdRange that this item exists, and should be pre-allocated an ID
    void NotifyRangeOfItem(const wxXmlNode* node, const wxString& item);
    // Tells all IDranges that they're now complete, and can create their IDs
    void FinaliseRanges(const wxXmlNode* node);

protected:
    std::vector<wxIdRange> m_IdRanges;

private:
    static wxIdRangeManager *ms_instance;
};

namespace
{

// helper used by DoFindResource() and elsewhere: returns true if this is an
// object or object_ref node
//
// node must be non-null
inline bool IsObjectNode(wxXmlNode *node)
{
    return node->GetType() == wxXML_ELEMENT_NODE &&
             (node->GetName() == wxS("object") ||
                node->GetName() == wxS("object_ref"));
}

// special XML attribute with name of input file, see GetFileNameFromNode()
const char *ATTR_INPUT_FILENAME = "__wx:filename";

// helper to get filename corresponding to an XML node
wxString
GetFileNameFromNode(const wxXmlNode *node, const wxXmlResourceDataRecords& files)
{
    // this loop does two things: it looks for ATTR_INPUT_FILENAME among
    // parents and if it isn't used, it finds the root of the XML tree 'node'
    // is in
    for ( ;; )
    {
        // in some rare cases (specifically, when an <object_ref> is used, see
        // wxXmlResource::CreateResFromNode() and MergeNodesOver()), we work
        // with XML nodes that are not rooted in any document from 'files'
        // (because a new node was created by CreateResFromNode() to merge the
        // content of <object_ref> and the referenced <object>); in that case,
        // we hack around the problem by putting the information about input
        // file into a custom attribute
        if ( node->HasAttribute(ATTR_INPUT_FILENAME) )
            return node->GetAttribute(ATTR_INPUT_FILENAME);

        if ( !node->GetParent() )
            break; // we found the root of this XML tree

        node = node->GetParent();
    }

    // NB: 'node' now points to the root of XML document

    for ( const wxXmlResourceDataRecord& rec : files )
    {
        if ( rec.Doc->GetRoot() == node )
        {
            return rec.File;
        }
    }

    return wxEmptyString; // not found
}

} // anonymous namespace


wxXmlResource *wxXmlResource::ms_instance = nullptr;

/*static*/ wxXmlResource *wxXmlResource::Get()
{
    if ( !ms_instance )
        ms_instance = new wxXmlResource;
    return ms_instance;
}

/*static*/ wxXmlResource *wxXmlResource::Set(wxXmlResource *res)
{
    wxXmlResource *old = ms_instance;
    ms_instance = res;
    return old;
}

wxXmlResource::wxXmlResource(int flags, const wxString& domain)
{
    m_flags = flags;
    m_version = -1;
    m_internal = new wxXmlResourceInternal;
    SetDomain(domain);
}

wxXmlResource::wxXmlResource(const wxString& filemask, int flags, const wxString& domain)
{
    m_flags = flags;
    m_version = -1;
    m_internal = new wxXmlResourceInternal;
    SetDomain(domain);
    Load(filemask);
}

wxXmlResource::~wxXmlResource()
{
    ClearHandlers();

    delete m_internal;
}

wxXmlResourceDataRecords& wxXmlResource::Data() const
{
    return m_internal->m_data;
}

void wxXmlResource::SetDomain(const wxString& domain)
{
    m_domain = domain;
}

void wxXmlResource::EnableFeature(const wxString& feature)
{
    m_internal->m_features.insert(feature);
}


/* static */
wxString wxXmlResource::ConvertFileNameToURL(const wxString& filename)
{
    wxString fnd(filename);

    // NB: as Load() and Unload() accept both filenames and URLs (should
    //     probably be changed to filenames only, but embedded resources
    //     currently rely on its ability to handle URLs - FIXME) we need to
    //     determine whether found name is filename and not URL and this is the
    //     fastest/simplest way to do it
    if (wxFileName::FileExists(fnd))
    {
        // Make the name absolute filename, because the app may
        // change working directory later:
        fnd = wxFileName(fnd).GetAbsolutePath();
#if wxUSE_FILESYSTEM
        fnd = wxFileSystem::FileNameToURL(fnd);
#endif
    }

    return fnd;
}

#if wxUSE_FILESYSTEM

/* static */
bool wxXmlResource::IsArchive(const wxString& filename)
{
    const wxString fnd = filename.Lower();

    return fnd.Matches(wxT("*.zip")) || fnd.Matches(wxT("*.xrs"));
}

#endif // wxUSE_FILESYSTEM

bool wxXmlResource::LoadFile(const wxFileName& file)
{
#if wxUSE_FILESYSTEM
    return Load(wxFileSystem::FileNameToURL(file));
#else
    return Load(file.GetFullPath());
#endif
}

bool wxXmlResource::LoadAllFiles(const wxString& dirname)
{
    bool ok = true;
    wxArrayString files;

    wxDir::GetAllFiles(dirname, &files, "*.xrc");

    for ( wxArrayString::const_iterator i = files.begin(); i != files.end(); ++i )
    {
        if ( !LoadFile(*i) )
            ok = false;
    }

    return ok;
}

bool wxXmlResource::Load(const wxString& filemask_)
{
    wxString filemask = ConvertFileNameToURL(filemask_);

    bool allOK = true,
         anyOK = false;

#if wxUSE_FILESYSTEM
    wxFileSystem fsys;
    auto const wxXmlFindFirst = [&]() { return fsys.FindFirst(filemask, wxFILE); };
    auto const wxXmlFindNext = [&]() { return fsys.FindNext(); };
#else
    auto const wxXmlFindFirst = [&]() { return wxFindFirstFile(filemask, wxFILE); };
    auto const wxXmlFindNext = [&]() { return wxFindNextFile(); };
#endif
    bool onlyThis = false;
    wxString fnd = wxXmlFindFirst();
    if ( fnd.empty() )
    {
        // Some file system handlers (e.g. wxInternetFSHandler) just don't
        // implement FindFirst() at all, try using the original path as is.
        fnd = filemask;

        // Don't try calling FindNext() if FindFirst() failed, just try loading
        // from this file.
        onlyThis = true;
    }

    while (!fnd.empty())
    {
        bool thisOK = true;

#if wxUSE_FILESYSTEM
        if ( IsArchive(fnd) )
        {
            if ( !Load(fnd + wxT("#zip:*.xrc")) )
                thisOK = false;
        }
        else // a single resource URL
#endif // wxUSE_FILESYSTEM
        {
            wxXmlDocument * const doc = DoLoadFile(fnd);
            if ( !doc )
                thisOK = false;
            else
                Data().emplace_back(fnd, doc);
        }

        if ( thisOK )
            anyOK = true;
        else
            allOK = false;

        if ( onlyThis )
            break;

        fnd = wxXmlFindNext();
    }

    if ( !anyOK )
    {
        wxLogError(_("Cannot load resources from '%s'."), filemask);
        return false;
    }

    return allOK;
}

bool wxXmlResource::Unload(const wxString& filename)
{
    wxASSERT_MSG( !wxIsWild(filename),
                    wxT("wildcards not supported by wxXmlResource::Unload()") );

    wxString fnd = ConvertFileNameToURL(filename);
#if wxUSE_FILESYSTEM
    const bool isArchive = IsArchive(fnd);
    if ( isArchive )
        fnd += wxT("#zip:");
#endif // wxUSE_FILESYSTEM

    bool unloaded = false;
    for ( wxXmlResourceDataRecords::iterator i = Data().begin();
          i != Data().end(); ++i )
    {
#if wxUSE_FILESYSTEM
        if ( isArchive )
        {
            if ( (*i).File.StartsWith(fnd) )
                unloaded = true;
            // don't break from the loop, we can have other matching files
        }
        else // a single resource URL
#endif // wxUSE_FILESYSTEM
        {
            if ( (*i).File == fnd )
            {
                Data().erase(i);
                unloaded = true;

                // no sense in continuing, there is only one file with this URL
                break;
            }
        }
    }

    return unloaded;
}


void wxXmlResource::AddHandler(wxXmlResourceHandler *handler)
{
    wxXmlResourceHandlerImpl *impl = new wxXmlResourceHandlerImpl(handler);
    handler->SetImpl(impl);
    m_internal->m_handlers.push_back(std::unique_ptr<wxXmlResourceHandler>{handler});
    handler->SetParentResource(this);
}

void wxXmlResource::InsertHandler(wxXmlResourceHandler *handler)
{
    wxXmlResourceHandlerImpl *impl = new wxXmlResourceHandlerImpl(handler);
    handler->SetImpl(impl);
    m_internal->m_handlers.insert(m_internal->m_handlers.begin(), std::unique_ptr<wxXmlResourceHandler>{handler});
    handler->SetParentResource(this);
}



void wxXmlResource::ClearHandlers()
{
    m_internal->m_handlers.clear();
}


wxMenu *wxXmlResource::LoadMenu(const wxString& name)
{
    return (wxMenu*)CreateResFromNode(FindResource(name, wxT("wxMenu")), nullptr, nullptr);
}



wxMenuBar *wxXmlResource::LoadMenuBar(wxWindow *parent, const wxString& name)
{
    return (wxMenuBar*)CreateResFromNode(FindResource(name, wxT("wxMenuBar")), parent, nullptr);
}



#if wxUSE_TOOLBAR
wxToolBar *wxXmlResource::LoadToolBar(wxWindow *parent, const wxString& name)
{
    return (wxToolBar*)CreateResFromNode(FindResource(name, wxT("wxToolBar")), parent, nullptr);
}
#endif


wxDialog *wxXmlResource::LoadDialog(wxWindow *parent, const wxString& name)
{
    return (wxDialog*)CreateResFromNode(FindResource(name, wxT("wxDialog")), parent, nullptr);
}

bool wxXmlResource::LoadDialog(wxDialog *dlg, wxWindow *parent, const wxString& name)
{
    return CreateResFromNode(FindResource(name, wxT("wxDialog")), parent, dlg) != nullptr;
}



wxPanel *wxXmlResource::LoadPanel(wxWindow *parent, const wxString& name)
{
    return (wxPanel*)CreateResFromNode(FindResource(name, wxT("wxPanel")), parent, nullptr);
}

bool wxXmlResource::LoadPanel(wxPanel *panel, wxWindow *parent, const wxString& name)
{
    return CreateResFromNode(FindResource(name, wxT("wxPanel")), parent, panel) != nullptr;
}

wxFrame *wxXmlResource::LoadFrame(wxWindow* parent, const wxString& name)
{
    return (wxFrame*)CreateResFromNode(FindResource(name, wxT("wxFrame")), parent, nullptr);
}

bool wxXmlResource::LoadFrame(wxFrame* frame, wxWindow *parent, const wxString& name)
{
    return CreateResFromNode(FindResource(name, wxT("wxFrame")), parent, frame) != nullptr;
}

wxBitmap wxXmlResource::LoadBitmap(const wxString& name)
{
    wxBitmap *bmp = (wxBitmap*)CreateResFromNode(
                               FindResource(name, wxT("wxBitmap")), nullptr, nullptr);
    wxBitmap rt;

    if (bmp) { rt = *bmp; delete bmp; }
    return rt;
}

wxIcon wxXmlResource::LoadIcon(const wxString& name)
{
    wxIcon *icon = (wxIcon*)CreateResFromNode(
                            FindResource(name, wxT("wxIcon")), nullptr, nullptr);
    wxIcon rt;

    if (icon) { rt = *icon; delete icon; }
    return rt;
}


wxObject *
wxXmlResource::DoLoadObject(wxWindow *parent,
                            const wxString& name,
                            const wxString& classname,
                            bool recursive)
{
    wxXmlNode * const node = FindResource(name, classname, recursive);

    return node ? DoCreateResFromNode(*node, parent, nullptr) : nullptr;
}

bool
wxXmlResource::DoLoadObject(wxObject *instance,
                            wxWindow *parent,
                            const wxString& name,
                            const wxString& classname,
                            bool recursive)
{
    wxXmlNode * const node = FindResource(name, classname, recursive);

    return node && DoCreateResFromNode(*node, parent, instance) != nullptr;
}


bool wxXmlResource::AttachUnknownControl(const wxString& name,
                                         wxWindow *control, wxWindow *parent)
{
    if (parent == nullptr)
        parent = control->GetParent();
    wxWindow *container = parent->FindWindow(name + wxT("_container"));
    if (!container)
    {
        wxLogError("Cannot find container for unknown control '%s'.", name);
        return false;
    }
    return control->Reparent(container);
}

// Small helper returning true if any of the tokens in the given string
// satisfies the given predicate.
template <typename Pred>
static bool HasAnyMatchingTokens(const wxString& s, const Pred& pred)
{
    wxStringTokenizer tkn(s, wxT(" |"));

    while (tkn.HasMoreTokens())
    {
        if ( pred(tkn.GetNextToken()) )
            return true;
    }

    return false;
}

// This function removes the nodes of the XRC document that are "inactive",
// i.e. shouldn't be taken into account at all, e.g. because they use a
// "platform" attribute not matching the current platform.
static void
FilterOurInactiveNodes(wxXmlNode *node,
                       const std::unordered_set<wxString>& features)
{
    static const wxString wxXRC_PLATFORM_ATTRIBUTE(wxS("platform"));
    static const wxString wxXRC_FEATURE_ATTRIBUTE(wxS("feature"));

    wxString s;

    wxXmlNode *c = node->GetChildren();
    while (c)
    {
        bool isok = true;
        if (c->GetAttribute(wxXRC_PLATFORM_ATTRIBUTE, &s))
        {
            isok = HasAnyMatchingTokens(s, [](const wxString& s)
                        { return wxPlatformId::MatchesCurrent(s); }
                    );
        }

        if (isok && c->GetAttribute(wxXRC_FEATURE_ATTRIBUTE, &s))
        {
            isok = HasAnyMatchingTokens(s, [&](const wxString& s)
                        { return features.count(s); }
                    );
        }

        if (isok)
        {
            FilterOurInactiveNodes(c, features);
            c = c->GetNext();
        }
        else
        {
            wxXmlNode *c2 = c->GetNext();
            node->RemoveChild(c);
            delete c;
            c = c2;
        }
    }
}

static void PreprocessForIdRanges(wxXmlNode *rootnode)
{
    // First go through the top level, looking for the names of ID ranges
    // as processing items is a lot easier if names are already known
    wxXmlNode *c = rootnode->GetChildren();
    while (c)
    {
        if (c->GetName() == wxT("ids-range"))
            wxIdRangeManager::Get()->AddRange(c);
        c = c->GetNext();
    }

    // Next, examine every 'name' for the '[' that denotes an ID in a range
    c = rootnode->GetChildren();
    while (c)
    {
        wxString name = c->GetAttribute(wxT("name"));
        if (name.find('[') != wxString::npos)
            wxIdRangeManager::Get()->NotifyRangeOfItem(rootnode, name);

        // Do any children by recursion, then proceed to the next sibling
        PreprocessForIdRanges(c);
        c = c->GetNext();
    }
}

bool wxXmlResource::UpdateResources()
{
    bool rt = true;

    // We never do it if this flag is specified.
    if ( m_flags & wxXRC_NO_RELOADING )
        return rt;

    for ( wxXmlResourceDataRecord& rec : Data() )
    {
        // Check if we need to reload this one.

        // And we don't do it for the records that were not loaded from a
        // file/URI (or at least not directly) in the first place.
        if ( !rec.Time.IsValid() )
            continue;

        // Otherwise check its modification time if we can.
#if wxUSE_DATETIME
        wxDateTime lastModTime = GetXRCFileModTime(rec.File);

        if ( lastModTime.IsValid() && lastModTime <= rec.Time )
#else // !wxUSE_DATETIME
        // Never reload the file contents: we can't know whether it changed or
        // not in this build configuration and it would be unexpected and
        // counter-productive to get a performance hit (due to constant
        // reloading of XRC files) in a minimal wx build which is presumably
        // used because of resource constraints of the current platform.
#endif // wxUSE_DATETIME/!wxUSE_DATETIME
        {
            // No need to reload, the file wasn't modified since we did it
            // last.
            continue;
        }

        wxXmlDocument * const doc = DoLoadFile(rec.File);
        if ( !doc )
        {
            // Notice that we keep the old XML document: it seems better to
            // preserve it instead of throwing it away if we have nothing to
            // replace it with.
            rt = false;
            continue;
        }

        // Replace the old resource contents with the new one.
        rec.Doc.reset(doc);

        // And, now that we loaded it successfully, update the last load time.
#if wxUSE_DATETIME
        rec.Time = lastModTime.IsValid() ? lastModTime : wxDateTime::Now();
#endif // wxUSE_DATETIME
    }

    return rt;
}

wxXmlDocument *wxXmlResource::DoLoadFile(const wxString& filename)
{
    wxLogTrace(wxT("xrc"), wxT("opening file '%s'"), filename);

    wxInputStream *stream = nullptr;

#if wxUSE_FILESYSTEM
    wxFileSystem fsys;
    std::unique_ptr<wxFSFile> file(fsys.OpenFile(filename));
    if (file)
    {
        // Notice that we don't have ownership of the stream in this case, it
        // remains owned by wxFSFile.
        stream = file->GetStream();
    }
#else // !wxUSE_FILESYSTEM
    wxFileInputStream fstream(filename);
    stream = &fstream;
#endif // wxUSE_FILESYSTEM/!wxUSE_FILESYSTEM

    if ( !stream || !stream->IsOk() )
    {
        wxLogError(_("Cannot open resources file '%s'."), filename);
        return nullptr;
    }

    std::unique_ptr<wxXmlDocument> doc(new wxXmlDocument);
    if (!doc->Load(*stream))
    {
        wxLogError(_("Cannot load resources from file '%s'."), filename);
        return nullptr;
    }

    if (!DoLoadDocument(*doc))
        return nullptr;

    return doc.release();
}

bool wxXmlResource::DoLoadDocument(const wxXmlDocument& doc)
{
    wxXmlNode * const root = doc.GetRoot();
    if (root->GetName() != wxT("resource"))
    {
        ReportError
        (
            root,
            "invalid XRC resource, doesn't have root node <resource>"
        );
        return false;
    }

    long version;
    int v1, v2, v3, v4;
    wxString verstr = root->GetAttribute(wxT("version"), wxT("0.0.0.0"));
    if (wxSscanf(verstr, wxT("%i.%i.%i.%i"), &v1, &v2, &v3, &v4) == 4)
        version = v1*256*256*256+v2*256*256+v3*256+v4;
    else
        version = 0;
    if (m_version == -1)
        m_version = version;
    if (m_version != version)
    {
        wxLogWarning("Resource files must have same version number.");
    }

    FilterOurInactiveNodes(root, m_internal->m_features);
    PreprocessForIdRanges(root);
    wxIdRangeManager::Get()->FinaliseRanges(root);

    return true;
}

bool wxXmlResource::LoadDocument(wxXmlDocument* doc, const wxString& name)
{
    wxCHECK_MSG( doc, false, wxS("must have a valid document") );

    if ( !DoLoadDocument(*doc) )
    {
        // Still avoid memory leaks.
        delete doc;
        return false;
    }

    // We need to use something instead of the file name, so if we were not
    // given a name synthesize something ourselves.
    wxString docname = name;
    if ( docname.empty() )
    {
        static unsigned long s_xrcDocument = 0;

        // Make it look different from any real file name.
        docname = wxString::Format(wxS("<XML document #%lu>"), ++s_xrcDocument);
    }

    Data().emplace_back(docname, doc, XRCWhence::From_Doc);

    return true;
}

wxXmlNode *wxXmlResource::DoFindResource(wxXmlNode *parent,
                                         const wxString& name,
                                         const wxString& classname,
                                         bool recursive) const
{
    wxXmlNode *node;

    // first search for match at the top-level nodes (as this is
    // where the resource is most commonly looked for):
    for (node = parent->GetChildren(); node; node = node->GetNext())
    {
        if ( IsObjectNode(node) && node->GetAttribute(wxS("name")) == name )
        {
            // empty class name matches everything
            if ( classname.empty() )
                return node;

            wxString cls(node->GetAttribute(wxS("class")));

            // object_ref may not have 'class' attribute:
            if (cls.empty() && node->GetName() == wxS("object_ref"))
            {
                wxString refName = node->GetAttribute(wxS("ref"));
                if (refName.empty())
                    continue;

                const wxXmlNode * const refNode = GetResourceNode(refName);
                if ( refNode )
                    cls = refNode->GetAttribute(wxS("class"));
            }

            if ( cls == classname )
                return node;
        }
    }

    // then recurse in child nodes
    if ( recursive )
    {
        for (node = parent->GetChildren(); node; node = node->GetNext())
        {
            if ( IsObjectNode(node) )
            {
                wxXmlNode* found = DoFindResource(node, name, classname, true);
                if ( found )
                    return found;
            }
        }
    }

    return nullptr;
}

wxXmlNode *wxXmlResource::FindResource(const wxString& name,
                                       const wxString& classname,
                                       bool recursive)
{
    wxString path;
    wxXmlNode * const
        node = GetResourceNodeAndLocation(name, classname, recursive, &path);

    if ( !node )
    {
        ReportError
        (
            nullptr,
            wxString::Format
            (
                "XRC resource \"%s\" (class \"%s\") not found",
                name, classname
            )
        );
    }
#if wxUSE_FILESYSTEM
    else // node was found
    {
        // ensure that relative paths work correctly when loading this node
        // (which should happen as soon as we return as FindResource() result
        // is always passed to CreateResFromNode())
        m_curFileSystem.ChangePathTo(path);
    }
#endif // wxUSE_FILESYSTEM

    return node;
}

wxXmlNode *
wxXmlResource::GetResourceNodeAndLocation(const wxString& name,
                                          const wxString& classname,
                                          bool recursive,
                                          wxString *path) const
{
    // ensure everything is up-to-date: this is needed to support on-demand
    // reloading of XRC files
    const_cast<wxXmlResource *>(this)->UpdateResources();

    for ( const wxXmlResourceDataRecord& rec : Data() )
    {
        wxXmlDocument * const doc = rec.Doc.get();
        if ( !doc || !doc->GetRoot() )
            continue;

        wxXmlNode * const
            found = DoFindResource(doc->GetRoot(), name, classname, recursive);
        if ( found )
        {
            if ( path )
                *path = rec.File;

            return found;
        }
    }

    return nullptr;
}

static void MergeNodesOver(wxXmlNode& dest, wxXmlNode& overwriteWith,
                           const wxString& overwriteFilename)
{
    // Merge attributes:
    for ( wxXmlAttribute *attr = overwriteWith.GetAttributes();
          attr; attr = attr->GetNext() )
    {
        wxXmlAttribute *dattr;
        for (dattr = dest.GetAttributes(); dattr; dattr = dattr->GetNext())
        {

            if ( dattr->GetName() == attr->GetName() )
            {
                dattr->SetValue(attr->GetValue());
                break;
            }
        }

        if ( !dattr )
            dest.AddAttribute(attr->GetName(), attr->GetValue());
   }

    // Merge child nodes:
    for (wxXmlNode* node = overwriteWith.GetChildren(); node; node = node->GetNext())
    {
        wxString name = node->GetAttribute(wxT("name"), wxEmptyString);
        wxXmlNode *dnode;

        for (dnode = dest.GetChildren(); dnode; dnode = dnode->GetNext() )
        {
            if ( dnode->GetName() == node->GetName() &&
                 dnode->GetAttribute(wxT("name"), wxEmptyString) == name &&
                 dnode->GetType() == node->GetType() )
            {
                MergeNodesOver(*dnode, *node, overwriteFilename);
                break;
            }
        }

        if ( !dnode )
        {
            wxXmlNode *copyOfNode = new wxXmlNode(*node);
            // remember referenced object's file, see GetFileNameFromNode()
            copyOfNode->AddAttribute(ATTR_INPUT_FILENAME, overwriteFilename);

            static const wxChar *AT_END = wxT("end");
            wxString insert_pos = node->GetAttribute(wxT("insert_at"), AT_END);
            if ( insert_pos == AT_END )
            {
                dest.AddChild(copyOfNode);
            }
            else if ( insert_pos == wxT("begin") )
            {
                dest.InsertChild(copyOfNode, dest.GetChildren());
            }
        }
    }

    if ( dest.GetType() == wxXML_TEXT_NODE && overwriteWith.GetContent().length() )
         dest.SetContent(overwriteWith.GetContent());
}

wxObject *
wxXmlResource::DoCreateResFromNode(wxXmlNode& node,
                                   wxObject *parent,
                                   wxObject *instance,
                                   wxXmlResourceHandler *handlerToUse)
{
    // handling of referenced resource
    if ( node.GetName() == wxT("object_ref") )
    {
        wxString refName = node.GetAttribute(wxT("ref"), wxEmptyString);
        wxXmlNode* refNode = FindResource(refName, wxEmptyString, true);

        if ( !refNode )
        {
            ReportError
            (
                &node,
                wxString::Format
                (
                    "referenced object node with ref=\"%s\" not found",
                    refName
                )
            );
            return nullptr;
        }

        const bool hasOnlyRefAttr = node.GetAttributes() != nullptr &&
                                    node.GetAttributes()->GetNext() == nullptr;

        if ( hasOnlyRefAttr && !node.GetChildren() )
        {
            // In the typical, simple case, <object_ref> is used to link
            // to another node and doesn't have any content of its own that
            // would overwrite linked object's properties. In this case,
            // we can simply create the resource from linked node.

            return DoCreateResFromNode(*refNode, parent, instance);
        }
        else
        {
            // In the more complicated (but rare) case, <object_ref> has
            // subnodes that partially overwrite content of the referenced
            // object. In this case, we need to merge both XML trees and
            // load the resource from result of the merge.

            wxXmlNode copy(*refNode);
            MergeNodesOver(copy, node, GetFileNameFromNode(&node, Data()));

            // remember referenced object's file, see GetFileNameFromNode()
            copy.AddAttribute(ATTR_INPUT_FILENAME,
                              GetFileNameFromNode(refNode, Data()));

            return DoCreateResFromNode(copy, parent, instance);
        }
    }

    if (handlerToUse)
    {
        if (handlerToUse->CanHandle(&node))
        {
            return handlerToUse->CreateResource(&node, parent, instance);
        }
    }
    else if (node.GetName() == wxT("object"))
    {
        for ( const auto& handler : m_internal->m_handlers )
        {
            if (handler->CanHandle(&node))
                return handler->CreateResource(&node, parent, instance);
        }
    }

    ReportError
    (
        &node,
        wxString::Format
        (
            "no handler found for XML node \"%s\" (class \"%s\")",
            node.GetName(),
            node.GetAttribute("class", wxEmptyString)
        )
    );
    return nullptr;
}

wxIdRange::wxIdRange(const wxXmlNode* node,
                     const wxString& rname,
                     const wxString& startno,
                     const wxString& rsize)
    : m_name(rname),
      m_start(0),
      m_size(0),
      m_item_end_found(0),
      m_finalised(0)
{
    long l;
    if ( startno.ToLong(&l) )
    {
        if ( l >= 0 )
        {
            m_start = l;
        }
        else
        {
            wxXmlResource::Get()->ReportError
            (
                node,
                "a negative id-range start parameter was given"
            );
        }
    }
    else
    {
        wxXmlResource::Get()->ReportError
        (
            node,
            "the id-range start parameter was malformed"
        );
    }

    unsigned long ul;
    if ( rsize.ToULong(&ul) )
    {
        m_size = ul;
    }
    else
    {
        wxXmlResource::Get()->ReportError
        (
            node,
            "the id-range size parameter was malformed"
        );
    }
}

void wxIdRange::NoteItem(const wxXmlNode* node, const wxString& item)
{
    // Nothing gets added here, but the existence of each item is noted
    // thus getting an accurate count. 'item' will be either an integer e.g.
    // [0] [123]: will eventually create an XRCID as start+integer or [start]
    // or [end] which are synonyms for [0] or [range_size-1] respectively.
    wxString content(item.Mid(1, item.length()-2));

    // Check that basename+item wasn't foo[]
    if (content.empty())
    {
        wxXmlResource::Get()->ReportError(node, "an empty id-range item found");
        return;
    }

    if (content=="start")
    {
        // "start" means [0], so store that in the set
        if (m_indices.count(0) == 0)
        {
            m_indices.insert(0);
        }
        else
        {
            wxXmlResource::Get()->ReportError
            (
                node,
                "duplicate id-range item found"
            );
        }
    }
    else if (content=="end")
    {
        // We can't yet be certain which XRCID this will be equivalent to, so
        // just note that there's an item with this name, in case we need to
        // inc the range size
        m_item_end_found = true;
    }
    else
    {
        // Anything else will be an integer, or rubbish
        unsigned long l;
        if ( content.ToULong(&l) )
        {
            if (m_indices.count(l) == 0)
            {
                m_indices.insert(l);
                // Check that this item wouldn't fall outside the current range
                // extent
                if (l >= m_size)
                {
                    m_size = l + 1;
                }
            }
            else
            {
                wxXmlResource::Get()->ReportError
                (
                    node,
                    "duplicate id-range item found"
                );
            }

        }
        else
        {
            wxXmlResource::Get()->ReportError
            (
                node,
                "an id-range item had a malformed index"
            );
        }
    }
}

void wxIdRange::Finalise(const wxXmlNode* node)
{
    wxCHECK_RET( !IsFinalised(),
                 "Trying to finalise an already-finalised range" );

    // Now we know about all the items, we can get an accurate range size
    // Expand any requested range-size if there were more items than would fit
    m_size = wxMax(m_size, m_indices.size());

    // If an item is explicitly called foo[end], ensure it won't clash with
    // another item
    if ( m_item_end_found && m_indices.count(m_size-1) )
        ++m_size;
    if (m_size == 0)
    {
        // This will happen if someone creates a range but no items in this xrc
        // file Report the error and abort, but don't finalise, in case items
        // appear later
        wxXmlResource::Get()->ReportError
        (
            node,
            "trying to create an empty id-range"
        );
        return;
    }

    if (m_start==0)
    {
        // This is the usual case, where the user didn't specify a start ID
        // So get the range using NewControlId().
        //
        // NB: negative numbers, but NewControlId already returns the most
        //     negative
        m_start = wxWindow::NewControlId(m_size);
        wxCHECK_RET( m_start != wxID_NONE,
                     "insufficient IDs available to create range" );
        m_end = m_start + m_size - 1;
    }
    else
    {
        // The user already specified a start value, which must be positive
        m_end = m_start + m_size - 1;
    }

    // Create the XRCIDs
    for (int i=m_start; i <= m_end; ++i)
    {
        // Ensure that we overwrite any existing value as otherwise
        // wxXmlResource::Unload() followed by Load() wouldn't work correctly.
        XRCID_Assign(m_name + wxString::Format("[%i]", i-m_start), i);

        wxLogTrace("xrcrange",
                   "integer = %i %s now returns %i",
                   i,
                   m_name + wxString::Format("[%i]", i-m_start),
                   XRCID((m_name + wxString::Format("[%i]", i-m_start)).mb_str()));
    }
    // and these special ones
    XRCID_Assign(m_name + "[start]", m_start);
    XRCID_Assign(m_name + "[end]", m_end);
    wxLogTrace("xrcrange","%s[start] = %i  %s[end] = %i",
            m_name.mb_str(),XRCID(wxString(m_name+"[start]").mb_str()),
                m_name.mb_str(),XRCID(wxString(m_name+"[end]").mb_str()));

    m_finalised = true;
}

wxIdRangeManager *wxIdRangeManager::ms_instance = nullptr;

/*static*/ wxIdRangeManager *wxIdRangeManager::Get()
{
    if ( !ms_instance )
        ms_instance = new wxIdRangeManager;
    return ms_instance;
}

/*static*/ wxIdRangeManager *wxIdRangeManager::Set(wxIdRangeManager *res)
{
    wxIdRangeManager *old = ms_instance;
    ms_instance = res;
    return old;
}

void wxIdRangeManager::AddRange(const wxXmlNode* node)
{
    wxString name = node->GetAttribute("name");
    wxString start = node->GetAttribute("start", "0");
    wxString size = node->GetAttribute("size", "0");
    if (name.empty())
    {
        wxXmlResource::Get()->ReportError
        (
            node,
            "xrc file contains an id-range without a name"
        );
        return;
    }

    for ( auto& idRange : m_IdRanges )
    {
        if ( idRange.GetName() == name )
        {
            // There was already a range with this name. Let's hope this is
            // from an Unload()/(re)Load(), not an unintentional duplication
            wxLogTrace("xrcrange",
                       "Replacing ID range, name=%s start=%s size=%s",
                       name, start, size);

            idRange = wxIdRange(node, name, start, size);
            return;
        }
    }

    wxLogTrace("xrcrange",
               "Adding ID range, name=%s start=%s size=%s",
               name, start, size);

    m_IdRanges.emplace_back(node, name, start, size);
}

void
wxIdRangeManager::NotifyRangeOfItem(const wxXmlNode* node,
                                    const wxString& item)
{
    wxString basename = item.BeforeFirst('[');
    wxCHECK_RET( !basename.empty(), "an id-range item without a range name" );

    for ( auto& idRange : m_IdRanges )
    {
        if ( idRange.GetName() == basename )
        {
            wxString value = item.Mid(basename.Len());
            if (value.at(value.length()-1)==']')
            {
                idRange.NoteItem(node, value);
            }
            else
            {
                wxXmlResource::Get()->ReportError(node, "a malformed id-range item");
            }

            // In any case, it's useless to continue.
            return;
        }
    }

    // Don't assert just because we've found an unexpected foo[123]
    // Someone might just want such a name, nothing to do with ranges
}

void wxIdRangeManager::FinaliseRanges(const wxXmlNode* node)
{
    for ( auto& idRange : m_IdRanges )
    {
        // Check if this range has already been finalised. Quite possible,
        // as  FinaliseRanges() gets called for each .xrc file loaded
        if (!idRange.IsFinalised())
        {
            wxLogTrace("xrcrange", "Finalising ID range %s", idRange.GetName());
            idRange.Finalise(node);
        }
    }
}


std::vector<std::unique_ptr<wxXmlSubclassFactory>>
wxXmlResourceInternal::ms_subclassFactories;

/*static*/ void wxXmlResource::AddSubclassFactory(wxXmlSubclassFactory *factory)
{
    wxXmlResourceInternal::ms_subclassFactories.push_back(std::unique_ptr<wxXmlSubclassFactory>{factory});
}

class wxXmlSubclassFactoryCXX : public wxXmlSubclassFactory
{
public:
    ~wxXmlSubclassFactoryCXX() {}

    wxObject *Create(const wxString& className) override
    {
        wxClassInfo* classInfo = wxClassInfo::FindClass(className);

        if (classInfo)
            return classInfo->CreateObject();
        else
            return nullptr;
    }
};




wxXmlResourceHandlerImpl::wxXmlResourceHandlerImpl(wxXmlResourceHandler *handler)
                         :wxXmlResourceHandlerImplBase(handler)
{
}

wxObject *wxXmlResourceHandlerImpl::CreateResFromNode(wxXmlNode *node,
                            wxObject *parent, wxObject *instance)
{
    return m_handler->m_resource->CreateResFromNode(node, parent, instance);
}

#if wxUSE_FILESYSTEM
wxFileSystem& wxXmlResourceHandlerImpl::GetCurFileSystem()
{
    return m_handler->m_resource->GetCurFileSystem();
}
#endif


wxObject *wxXmlResourceHandlerImpl::CreateResource(wxXmlNode *node, wxObject *parent, wxObject *instance)
{
    wxXmlNode *myNode = m_handler->m_node;
    wxString myClass = m_handler->m_class;
    wxObject *myParent = m_handler->m_parent, *myInstance = m_handler->m_instance;
    wxWindow *myParentAW = m_handler->m_parentAsWindow;

    m_handler->m_instance = instance;
    if (!m_handler->m_instance && node->HasAttribute(wxT("subclass")) &&
        !(m_handler->m_resource->GetFlags() & wxXRC_NO_SUBCLASSING))
    {
        wxString subclass = node->GetAttribute(wxT("subclass"), wxEmptyString);
        if (!subclass.empty())
        {
            for (auto& factory : wxXmlResourceInternal::ms_subclassFactories)
            {
                m_handler->m_instance = factory->Create(subclass);
                if (m_handler->m_instance)
                    break;
            }

            if (!m_handler->m_instance)
            {
                wxString name = node->GetAttribute(wxT("name"), wxEmptyString);
                ReportError
                (
                    node,
                    wxString::Format
                    (
                        "subclass \"%s\" not found for resource \"%s\", not subclassing",
                        subclass, name
                    )
                );
            }
        }
    }

    m_handler->m_node = node;
    m_handler->m_class = node->GetAttribute(wxT("class"), wxEmptyString);
    m_handler->m_parent = parent;
    m_handler->m_parentAsWindow = wxDynamicCast(m_handler->m_parent, wxWindow);

    wxObject *returned = GetHandler()->DoCreateResource();

    m_handler->m_node = myNode;
    m_handler->m_class = myClass;
    m_handler->m_parent = myParent; m_handler->m_parentAsWindow = myParentAW;
    m_handler->m_instance = myInstance;

    return returned;
}

bool wxXmlResourceHandlerImpl::HasParam(const wxString& param)
{
    return (GetParamNode(param) != nullptr);
}


int wxXmlResourceHandlerImpl::GetStyle(const wxString& param, int defaults)
{
    wxString s = GetParamValue(param);

    if (s.empty()) return defaults;

    wxStringTokenizer tkn(s, wxT("| \t\n"), wxTOKEN_STRTOK);
    int style = 0;
    int index;
    wxString fl;
    while (tkn.HasMoreTokens())
    {
        fl = tkn.GetNextToken();
        index = m_handler->m_styleNames.Index(fl);
        if (index != wxNOT_FOUND)
        {
            style |= m_handler->m_styleValues[index];
        }
        else
        {
            ReportParamError
            (
                param,
                wxString::Format("unknown style flag \"%s\"", fl)
            );
        }
    }
    return style;
}



wxString wxXmlResourceHandlerImpl::GetNodeText(const wxXmlNode* node, int flags)
{
    wxString str1(GetNodeContent(node));
    if ( str1.empty() )
        return str1;

    wxString str2;

    if ( !(flags & wxXRC_TEXT_NO_ESCAPE) )
    {
        // "\\" wasn't translated to "\" prior to 2.5.3.0:
        const bool escapeBackslash = (m_handler->m_resource->CompareVersion(2,5,3,0) >= 0);

        // VS: First version of XRC resources used $ instead of & (which is
        //     illegal in XML), but later I realized that '_' fits this purpose
        //     much better (because &File means "File with F underlined").
        const wxChar amp_char = (m_handler->m_resource->CompareVersion(2,3,0,1) < 0)
                                ? '$' : '_';

        for ( wxString::const_iterator dt = str1.begin(); dt != str1.end(); ++dt )
        {
            // Remap amp_char to &, map double amp_char to amp_char (for things
            // like "&File..." -- this is illegal in XML, so we use "_File..."):
            if ( *dt == amp_char )
            {
                if ( dt+1 == str1.end() || *(++dt) == amp_char )
                    str2 << amp_char;
                else
                    str2 << wxT('&') << *dt;
            }
            // Remap \n to CR, \r to LF, \t to TAB, \\ to \:
            else if ( *dt == wxT('\\') )
            {
                switch ( (*(++dt)).GetValue() )
                {
                    case wxT('n'):
                        str2 << wxT('\n');
                        break;

                    case wxT('t'):
                        str2 << wxT('\t');
                        break;

                    case wxT('r'):
                        str2 << wxT('\r');
                        break;

                    case wxT('\\') :
                        // "\\" wasn't translated to "\" prior to 2.5.3.0:
                        if ( escapeBackslash )
                        {
                            str2 << wxT('\\');
                            break;
                        }
                        wxFALLTHROUGH;// else fall-through to default: branch below

                    default:
                        str2 << wxT('\\') << *dt;
                        break;
                }
            }
            else
            {
                str2 << *dt;
            }
        }
    }
    else // Don't escape anything in this string.
    {
        // We won't use str1 at all, so move its contents to str2.
        str2.swap(str1);
    }

    if (m_handler->m_resource->GetFlags() & wxXRC_USE_LOCALE)
    {
        if (!(flags & wxXRC_TEXT_NO_TRANSLATE) && node &&
            node->GetAttribute(wxT("translate"), wxEmptyString) != wxT("0"))
        {
            return wxGetTranslation(str2, m_handler->m_resource->GetDomain());
        }
        else
        {
            return str2;
        }
    }

    // If wxXRC_USE_LOCALE is not set, then the string is already in
    // system's default encoding in ANSI build, so we don't have to
    // do anything special here.
    return str2;
}



long wxXmlResourceHandlerImpl::GetLong(const wxString& param, long defaultv)
{
    long value = defaultv;
    wxString str1 = GetParamValue(param);

    if (!str1.empty())
    {
        if (!str1.ToLong(&value))
        {
            ReportParamError
            (
                param,
                wxString::Format("invalid long specification \"%s\"", str1)
            );
        }
    }

    return value;
}

float wxXmlResourceHandlerImpl::GetFloat(const wxString& param, float defaultv)
{
    wxString str = GetParamValue(param);

    // strings in XRC always use C locale so make sure to use the
    // locale-independent wxString::ToCDouble() and not ToDouble() which uses
    // the current locale with a potentially different decimal point character
    double value = double(defaultv);
    if (!str.empty())
    {
        if (!str.ToCDouble(&value))
        {
            ReportParamError
            (
                param,
                wxString::Format("invalid float specification \"%s\"", str)
            );
        }
    }

    return wx_truncate_cast(float, value);
}


int wxXmlResourceHandlerImpl::GetID()
{
    return wxXmlResource::GetXRCID(GetName());
}



wxString wxXmlResourceHandlerImpl::GetName()
{
    return m_handler->m_node->GetAttribute(wxT("name"), wxT("-1"));
}



bool wxXmlResourceHandlerImpl::GetBoolAttr(const wxString& attr, bool defaultv)
{
    wxString v;
    return m_handler->m_node->GetAttribute(attr, &v) ? v == '1' : defaultv;
}

bool wxXmlResourceHandlerImpl::GetBool(const wxString& param, bool defaultv)
{
    const wxString v = GetParamValue(param);

    return v.empty() ? defaultv : (v == '1');
}


static wxColour GetSystemColour(const wxString& name)
{
    if (!name.empty())
    {
        #define SYSCLR(clr) \
            if (name == wxT(#clr)) return wxSystemSettings::GetColour(clr);
        SYSCLR(wxSYS_COLOUR_SCROLLBAR)
        SYSCLR(wxSYS_COLOUR_BACKGROUND)
        SYSCLR(wxSYS_COLOUR_DESKTOP)
        SYSCLR(wxSYS_COLOUR_ACTIVECAPTION)
        SYSCLR(wxSYS_COLOUR_INACTIVECAPTION)
        SYSCLR(wxSYS_COLOUR_MENU)
        SYSCLR(wxSYS_COLOUR_WINDOW)
        SYSCLR(wxSYS_COLOUR_WINDOWFRAME)
        SYSCLR(wxSYS_COLOUR_MENUTEXT)
        SYSCLR(wxSYS_COLOUR_WINDOWTEXT)
        SYSCLR(wxSYS_COLOUR_CAPTIONTEXT)
        SYSCLR(wxSYS_COLOUR_ACTIVEBORDER)
        SYSCLR(wxSYS_COLOUR_INACTIVEBORDER)
        SYSCLR(wxSYS_COLOUR_APPWORKSPACE)
        SYSCLR(wxSYS_COLOUR_HIGHLIGHT)
        SYSCLR(wxSYS_COLOUR_HIGHLIGHTTEXT)
        SYSCLR(wxSYS_COLOUR_BTNFACE)
        SYSCLR(wxSYS_COLOUR_3DFACE)
        SYSCLR(wxSYS_COLOUR_BTNSHADOW)
        SYSCLR(wxSYS_COLOUR_3DSHADOW)
        SYSCLR(wxSYS_COLOUR_GRAYTEXT)
        SYSCLR(wxSYS_COLOUR_BTNTEXT)
        SYSCLR(wxSYS_COLOUR_INACTIVECAPTIONTEXT)
        SYSCLR(wxSYS_COLOUR_BTNHIGHLIGHT)
        SYSCLR(wxSYS_COLOUR_BTNHILIGHT)
        SYSCLR(wxSYS_COLOUR_3DHIGHLIGHT)
        SYSCLR(wxSYS_COLOUR_3DHILIGHT)
        SYSCLR(wxSYS_COLOUR_3DDKSHADOW)
        SYSCLR(wxSYS_COLOUR_3DLIGHT)
        SYSCLR(wxSYS_COLOUR_INFOTEXT)
        SYSCLR(wxSYS_COLOUR_INFOBK)
        SYSCLR(wxSYS_COLOUR_LISTBOX)
        SYSCLR(wxSYS_COLOUR_HOTLIGHT)
        SYSCLR(wxSYS_COLOUR_GRADIENTACTIVECAPTION)
        SYSCLR(wxSYS_COLOUR_GRADIENTINACTIVECAPTION)
        SYSCLR(wxSYS_COLOUR_MENUHILIGHT)
        SYSCLR(wxSYS_COLOUR_MENUBAR)
        #undef SYSCLR
    }

    return wxNullColour;
}

wxColour
wxXmlResourceHandlerImpl::GetColour(const wxString& param,
                                    const wxColour& defaultLight,
                                    const wxColour& defaultDark)
{
    const wxString& values = GetParamValue(param);

    if ( values.empty() )
        return wxSystemSettings::SelectLightDark(defaultLight, defaultDark);

    wxStringTokenizer tk(values, "|");
    wxString v = tk.GetNextToken();

    while ( tk.HasMoreTokens() )
    {
        wxString altCol = tk.GetNextToken();
        wxString altV;
        if ( altCol.StartsWith("dark:", &altV) )
        {
            if ( wxSystemSettings::GetAppearance().IsDark() )
                v = altV;
            //else: just ignore it, it's valid but not used
        }
        else
        {
            ReportParamError
            (
                param,
                wxString::Format
                (
                    "unrecognized alternative colour specification \"%s\"",
                    altCol
                )
            );
            return wxNullColour;
        }
    }

    wxColour clr;

    // wxString -> wxColour conversion
    if (!clr.Set(v))
    {
        // the colour doesn't use #RRGGBB format, check if it is symbolic
        // colour name:
        clr = GetSystemColour(v);
        if (clr.IsOk())
            return clr;

        ReportParamError
        (
            param,
            wxString::Format("incorrect colour specification \"%s\"", v)
        );
        return wxNullColour;
    }

    return clr;
}

namespace
{

// if 'param' has stock_id/stock_client, extracts them and returns true
bool GetStockArtAttrs(const wxXmlNode *paramNode,
                      const wxString& defaultArtClient,
                      wxString& art_id, wxString& art_client)
{
    if ( paramNode )
    {
        art_id = paramNode->GetAttribute("stock_id", "");

        if ( !art_id.empty() )
        {
            art_id = wxART_MAKE_ART_ID_FROM_STR(art_id);

            art_client = paramNode->GetAttribute("stock_client", "");
            if ( art_client.empty() )
                art_client = defaultArtClient;
            else
                art_client = wxART_MAKE_CLIENT_ID_FROM_STR(art_client);

            return true;
        }
    }

    return false;
}

// Load a bitmap from a file system element.
wxBitmap LoadBitmapFromFS(wxXmlResourceHandlerImpl* impl,
                            const wxString& path,
                            wxSize size,
                            const wxString& nodeName)
{
    if (path.empty()) return wxNullBitmap;
#if wxUSE_FILESYSTEM
    wxFSFile *fsfile = impl->GetCurFileSystem().OpenFile(path, wxFS_READ | wxFS_SEEKABLE);
    if (fsfile == nullptr)
    {
        impl->ReportParamError
        (
            nodeName,
            wxString::Format("cannot open bitmap resource \"%s\"", path)
        );
        return wxNullBitmap;
    }
    wxImage img(*(fsfile->GetStream()));
    delete fsfile;
#else
    wxImage img(path);
#endif

    if (!img.IsOk())
    {
        impl->ReportParamError
        (
            nodeName,
            wxString::Format("cannot create bitmap from \"%s\"", path)
        );
        return wxNullBitmap;
    }
    if (!(size == wxDefaultSize)) img.Rescale(size.x, size.y);
    return wxBitmap(img);
}

// forward declaration
template <typename T>
inline
bool XRCConvertFromAbsValue(const wxString& s, T& value);

} // anonymous namespace

wxBitmap wxXmlResourceHandlerImpl::GetBitmap(const wxString& param,
                                         const wxArtClient& defaultArtClient,
                                         wxSize size)
{
    // it used to be possible to pass an empty string here to indicate that the
    // bitmap name should be read from this node itself but this is not
    // supported any more because GetBitmap(m_node) can be used directly
    // instead
    wxASSERT_MSG( !param.empty(), "bitmap parameter name can't be empty" );

    const wxXmlNode* const node = GetParamNode(param);

    if ( !node )
    {
        // this is not an error as bitmap parameter could be optional
        return wxNullBitmap;
    }

    return GetBitmap(node, defaultArtClient, size);
}

wxBitmap wxXmlResourceHandlerImpl::GetBitmap(const wxXmlNode* node,
                                         const wxArtClient& defaultArtClient,
                                         wxSize size)
{
    wxCHECK_MSG( node, wxNullBitmap, "bitmap node can't be null" );

    /* If the bitmap is specified as stock item, query wxArtProvider for it: */
    wxString art_id, art_client;
    if ( GetStockArtAttrs(node, defaultArtClient,
                          art_id, art_client) )
    {
        wxBitmap stockArt(wxArtProvider::GetBitmap(art_id, art_client, size));
        if ( stockArt.IsOk() )
            return stockArt;
    }

    /* ...or load the bitmap from file: */
    return LoadBitmapFromFS(this, GetFilePath(node), size, node->GetName());
}


wxBitmapBundle wxXmlResourceHandlerImpl::GetBitmapBundle(const wxString& param,
                                                     const wxArtClient& defaultArtClient,
                                                     wxSize size)
{
    wxASSERT_MSG( !param.empty(), "bitmap bundle parameter name can't be empty" );

    const wxXmlNode* const node = GetParamNode(param);

    if ( !node )
    {
        // this is not an error as bitmap parameter could be optional
        return wxBitmapBundle();
    }

    return GetBitmapBundle(node, defaultArtClient, size);
}

wxBitmapBundle
wxXmlResourceHandlerImpl::GetBitmapBundle(const wxXmlNode* node,
                                          const wxArtClient& defaultArtClient,
                                          wxSize size)
{
    if  ( !node )
        return wxBitmapBundle();

    /* If the bitmap is specified as stock item, query wxArtProvider for it: */
    wxString art_id, art_client;
    if ( GetStockArtAttrs(node, defaultArtClient,
                          art_id, art_client) )
    {
        wxBitmapBundle stockArt(wxArtProvider::GetBitmapBundle(art_id, art_client, size));
        if ( stockArt.IsOk() )
            return stockArt;
    }

    wxBitmapBundle bitmapBundle;
    wxString paramValue = GetFilePath(node);
    if ( paramValue.EndsWith(".svg") )
    {
        if ( paramValue.Contains(";") )
        {
            ReportParamError
            (
                node->GetName(),
                "may contain either one SVG file or a list of files separated by ';'"
            );
            return bitmapBundle;
        }

        // it is a bundle from svg file
        wxString svgDefaultSizeAttr = node->GetAttribute("default_size", "");
        if ( svgDefaultSizeAttr.empty() )
        {
            ReportParamError
            (
             node->GetName(),
                "'default_size' attribute required with svg file"
            );
        }
        else
        {
#ifdef wxHAS_SVG
            wxSize svgDefaultSize;
            if ( !XRCConvertFromAbsValue(svgDefaultSizeAttr, svgDefaultSize) )
            {
                ReportParamError
                (
                     node->GetName(),
                     wxString::Format("cannot parse \"%s\" as SVG size",
                                      svgDefaultSizeAttr)
                );
                return bitmapBundle;
            }
#if wxUSE_FILESYSTEM
            wxFSFile* fsfile = GetCurFileSystem().OpenFile(paramValue, wxFS_READ | wxFS_SEEKABLE);
            if (fsfile == nullptr)
            {
                ReportParamError
                (
                    node->GetName(),
                    wxString::Format("cannot open SVG resource \"%s\"", paramValue)
                );
            }
            else
            {
                wxInputStream* s = fsfile->GetStream();
                const size_t len = static_cast<size_t>(s->GetLength());
                wxCharBuffer buf(len);
                char* const ptr = buf.data();

                if (s->ReadAll(ptr, len))
                {
                    bitmapBundle = wxBitmapBundle::FromSVG(ptr, svgDefaultSize);
                }
                delete fsfile;
            }
#else
            bitmapBundle = wxBitmapBundle::FromSVGFile(paramValue, svgDefaultSize);
#endif
#else // !wxHAS_SVG
            ReportParamError
            (
                node->GetName(),
                "SVG bitmaps are not supported in this build of the library"
            );
#endif // wxHAS_SVG/!wxHAS_SVG
        }
    }
    else
    {
        if ( paramValue.Contains(".svg;") )
        {
            ReportParamError
            (
                node->GetName(),
                "may contain either one SVG file or a list of files separated by ';'"
            );
            return bitmapBundle;
        }

        // it is a bundle from bitmaps
        wxVector<wxBitmap> bitmaps;
        wxArrayString paths = wxSplit(paramValue, ';', '\0');
        for ( wxArrayString::const_iterator i = paths.begin(); i != paths.end(); ++i )
        {
            wxBitmap bmpNext = LoadBitmapFromFS(this, *i, size, node->GetName());
            if ( !bmpNext.IsOk() )
            {
                // error in loading wxBitmap, return invalid wxBitmapBundle
                return bitmapBundle;
            }
            bitmaps.push_back(bmpNext);
        }
        bitmapBundle = wxBitmapBundle::FromBitmaps(bitmaps);
    }

    return bitmapBundle;
}


wxIcon wxXmlResourceHandlerImpl::GetIcon(const wxString& param,
                                     const wxArtClient& defaultArtClient,
                                     wxSize size)
{
    // see comment in GetBitmap(wxString) overload
    wxASSERT_MSG( !param.empty(), "icon parameter name can't be empty" );

    const wxXmlNode* const node = GetParamNode(param);

    if ( !node )
    {
        // this is not an error as icon parameter could be optional
        return wxIcon();
    }

    return GetIcon(node, defaultArtClient, size);
}

wxIcon wxXmlResourceHandlerImpl::GetIcon(const wxXmlNode* node,
                                     const wxArtClient& defaultArtClient,
                                     wxSize size)
{
    wxIcon icon;
    icon.CopyFromBitmap(GetBitmap(node, defaultArtClient, size));
    return icon;
}


wxIconBundle wxXmlResourceHandlerImpl::GetIconBundle(const wxString& param,
                                                 const wxArtClient& defaultArtClient)
{
    wxString art_id, art_client;
    if ( GetStockArtAttrs(GetParamNode(param), defaultArtClient,
                          art_id, art_client) )
    {
        wxIconBundle stockArt(wxArtProvider::GetIconBundle(art_id, art_client));
        if ( stockArt.IsOk() )
            return stockArt;
    }

    const wxString name = GetFilePath(GetParamNode(param));
    if ( name.empty() )
        return wxNullIconBundle;

#if wxUSE_FILESYSTEM
    wxFSFile *fsfile = GetCurFileSystem().OpenFile(name, wxFS_READ | wxFS_SEEKABLE);
    if ( fsfile == nullptr )
    {
        ReportParamError
        (
            param,
            wxString::Format("cannot open icon resource \"%s\"", name)
        );
        return wxNullIconBundle;
    }

    wxIconBundle bundle(*(fsfile->GetStream()));
    delete fsfile;
#else
    wxIconBundle bundle(name);
#endif

    if ( !bundle.IsOk() )
    {
        ReportParamError
        (
            param,
            wxString::Format("cannot create icon from \"%s\"", name)
        );
        return wxNullIconBundle;
    }

    return bundle;
}


wxImageList *wxXmlResourceHandlerImpl::GetImageList(const wxString& param)
{
    wxXmlNode * const imagelist_node = GetParamNode(param);
    if ( !imagelist_node )
        return nullptr;

    wxXmlNode * const oldnode = m_handler->m_node;
    m_handler->m_node = imagelist_node;

    // Get the size if we have it, otherwise we will use the size of the first
    // list element.
    wxSize size = GetSize();

    // Start adding images, we'll create the image list when adding the first
    // one.
    wxImageList * imagelist = nullptr;
    wxString parambitmap = wxT("bitmap");
    if ( HasParam(parambitmap) )
    {
        wxXmlNode *n = m_handler->m_node->GetChildren();
        while (n)
        {
            if (n->GetType() == wxXML_ELEMENT_NODE && n->GetName() == parambitmap)
            {
                wxIcon icon = GetIcon(n, wxART_OTHER, size);
                if ( !imagelist )
                {
                    // We need the real image list size to create it.
                    if ( size == wxDefaultSize )
                        size = icon.GetSize();

                    // We use the mask by default.
                    bool mask = GetBool(wxS("mask"), true);

                    imagelist = new wxImageList(size.x, size.y, mask);
                }

                // add icon instead of bitmap to keep the bitmap mask
                imagelist->Add(icon);
            }
            n = n->GetNext();
        }
    }

    m_handler->m_node = oldnode;
    return imagelist;
}

wxString wxXmlResourceHandlerImpl::GetFilePath(const wxXmlNode* node)
{
    wxString path = GetParamValue(node);

    if ( m_handler->m_resource->GetFlags() & wxXRC_USE_ENVVARS )
        path = wxExpandEnvVars(path);

    return path;
}

wxXmlNode *wxXmlResourceHandlerImpl::GetParamNode(const wxString& param)
{
    wxCHECK_MSG(m_handler->m_node, nullptr, wxT("You can't access handler data before it was initialized!"));

    wxXmlNode *n = m_handler->m_node->GetChildren();

    while (n)
    {
        if (n->GetType() == wxXML_ELEMENT_NODE && n->GetName() == param)
        {
            // TODO: check that there are no other properties/parameters with
            //       the same name and log an error if there are (can't do this
            //       right now as I'm not sure if it's not going to break code
            //       using this function in unintentional way (i.e. for
            //       accessing other things than properties), for example
            //       wxBitmapComboBoxXmlHandler almost surely does
            return n;
        }
        n = n->GetNext();
    }
    return nullptr;
}

bool wxXmlResourceHandlerImpl::IsOfClass(wxXmlNode *node, const wxString& classname) const
{
    return node->GetAttribute(wxT("class")) == classname;
}


bool wxXmlResourceHandlerImpl::IsObjectNode(const wxXmlNode *node) const
{
    return node &&
            node->GetType() == wxXML_ELEMENT_NODE &&
                (node->GetName() == wxS("object") ||
                    node->GetName() == wxS("object_ref"));
}

wxString wxXmlResourceHandlerImpl::GetNodeName(const wxXmlNode *node) const
{
    return node ? node->GetName() : wxString{};
}

wxString
wxXmlResourceHandlerImpl::GetNodeAttribute(const wxXmlNode *node,
                                           const wxString& attrName,
                                           const wxString& defaultValue) const
{
    return node ? node->GetAttribute(attrName, defaultValue) : defaultValue;
}

wxString wxXmlResourceHandlerImpl::GetNodeContent(const wxXmlNode *node) const
{
    return node ? node->GetNodeContent() : wxString{};
}

wxXmlNode *wxXmlResourceHandlerImpl::GetNodeParent(const wxXmlNode *node) const
{
    return node ? node->GetParent() : nullptr;
}

wxXmlNode *wxXmlResourceHandlerImpl::GetNodeNext(const wxXmlNode *node) const
{
    return node ? node->GetNext() : nullptr;
}

wxXmlNode *wxXmlResourceHandlerImpl::GetNodeChildren(const wxXmlNode *node) const
{
    return node ? node->GetChildren() : nullptr;
}



wxString wxXmlResourceHandlerImpl::GetParamValue(const wxString& param)
{
    if (param.empty())
        return GetNodeContent(m_handler->m_node);
    else
        return GetNodeContent(GetParamNode(param));
}

wxString wxXmlResourceHandlerImpl::GetParamValue(const wxXmlNode* node)
{
    return GetNodeContent(node);
}

namespace
{

// Dimensions (linear or sizes/positions) can be expressed as absolute values
// or as dialog units in XRC, define functions for parsing both of them.

bool XRCConvertFromAbsValue(const wxString& s, int& value)
{
    long l;
    if ( !s.ToLong(&l) )
        return false;

    if ( l > INT_MAX )
        return false;

    value = static_cast<int>(l);
    return true;
}

template <typename T>
inline
bool XRCConvertFromAbsValue(const wxString& s, T& value)
{
    return XRCConvertFromAbsValue(s.BeforeFirst(','), value.x) &&
            XRCConvertFromAbsValue(s.AfterLast(wxS(',')), value.y);
}

inline
void XRCConvertFromDLU(wxWindow* w, int& value)
{
    value = w->ConvertDialogToPixels(wxPoint(value, 0)).x;
}

template <typename T>
inline
void XRCConvertFromDLU(wxWindow* w, T& value)
{
    value = w->ConvertDialogToPixels(value);
}

// Helper for parsing strings which contain values (of type T, for which
// XRCConvertFromAbsValue() and XRCConvertFromDLU() functions must be defined)
// which can be expressed either in pixels or dialog units.
template <typename T>
T
ParseStringInPixels(wxXmlResourceHandlerImpl* impl,
                   const wxString& param,
                   const wxString& s,
                   const T& defaultValue,
                   wxWindow *windowToUse)
{
    if ( s.empty() )
        return defaultValue;

    const bool inDLU = s.Last() == 'd';

    T value;
    if ( !XRCConvertFromAbsValue(inDLU ? wxString(s).RemoveLast() : s, value) )
    {
        impl->ReportParamError
              (
               param,
               wxString::Format("cannot parse dimension value \"%s\"", s)
              );
        return defaultValue;
    }

    if ( !windowToUse )
        windowToUse = impl->GetParentAsWindow();

    if ( inDLU )
    {
        if ( !windowToUse )
        {
            impl->ReportParamError
                  (
                   param,
                   wxString::Format("cannot interpret dimension value \"%s\" "
                                    "in dialog units without a window", s)
                  );
            return defaultValue;
        }

        XRCConvertFromDLU(windowToUse, value);
    }
    else // The value is in resolution-independent pixels.
    {
        value = wxWindow::FromDIP(value, windowToUse);
    }

    return value;
}

// Helper for parsing values which uses ParseStringInPixels() above.
template <typename T>
T
ParseValueInPixels(wxXmlResourceHandlerImpl* impl,
                   const wxString& param,
                   const T& defaultValue,
                   wxWindow *windowToUse = nullptr)
{
    return ParseStringInPixels(impl, param, impl->GetParamValue(param), defaultValue, windowToUse);
}

} // anonymous namespace

wxSize wxXmlResourceHandlerImpl::GetSize(const wxString& param,
                                     wxWindow *windowToUse)
{
    return ParseValueInPixels(this, param, wxDefaultSize, windowToUse);
}



wxPoint wxXmlResourceHandlerImpl::GetPosition(const wxString& param,
                                              wxWindow *windowToUse)
{
    return ParseValueInPixels(this, param, wxDefaultPosition, windowToUse);
}



wxCoord wxXmlResourceHandlerImpl::GetDimension(const wxString& param,
                                           wxCoord defaultv,
                                           wxWindow *windowToUse)
{
    return ParseValueInPixels(this, param, defaultv, windowToUse);
}


wxSize wxXmlResourceHandlerImpl::GetPairInts(const wxString& param)
{
    const wxString s = GetParamValue(param);
    if ( s.empty() )
        return wxDefaultSize;

    wxSize sz;
    if ( !XRCConvertFromAbsValue(s, sz) )
    {
        ReportParamError
              (
               param,
               wxString::Format("cannot parse \"%s\" as pair of integers", s)
              );
        return wxDefaultSize;
    }

    return sz;
}


wxDirection
wxXmlResourceHandlerImpl::GetDirection(const wxString& param, wxDirection dirDefault)
{
    wxDirection dir;

    const wxString dirstr = GetParamValue(param);
    if ( dirstr.empty() )
        dir = dirDefault;
    else if ( dirstr == "wxLEFT" )
        dir = wxLEFT;
    else if ( dirstr == "wxRIGHT" )
        dir = wxRIGHT;
    else if ( dirstr == "wxTOP" )
        dir = wxTOP;
    else if ( dirstr == "wxBOTTOM" )
        dir = wxBOTTOM;
    else
    {
        ReportError
        (
            GetParamNode(param),
            wxString::Format
            (
                "Invalid direction \"%s\": must be one of "
                "wxLEFT|wxRIGHT|wxTOP|wxBOTTOM.",
                dirstr
            )
        );

        dir = dirDefault;
    }

    return dir;
}

// Get system font index using indexname
static wxFont GetSystemFont(const wxString& name)
{
    if (!name.empty())
    {
        #define SYSFNT(fnt) \
            if (name == wxT(#fnt)) return wxSystemSettings::GetFont(fnt);
        SYSFNT(wxSYS_OEM_FIXED_FONT)
        SYSFNT(wxSYS_ANSI_FIXED_FONT)
        SYSFNT(wxSYS_ANSI_VAR_FONT)
        SYSFNT(wxSYS_SYSTEM_FONT)
        SYSFNT(wxSYS_DEVICE_DEFAULT_FONT)
        SYSFNT(wxSYS_SYSTEM_FIXED_FONT)
        SYSFNT(wxSYS_DEFAULT_GUI_FONT)
        #undef SYSFNT
    }

    return wxNullFont;
}

wxFont wxXmlResourceHandlerImpl::GetFont(const wxString& param, wxWindow* parent)
{
    wxXmlNode *font_node = GetParamNode(param);
    if (font_node == nullptr)
    {
        ReportError(
            wxString::Format("cannot find font node \"%s\"", param));
        return wxNullFont;
    }

    wxXmlNode *oldnode = m_handler->m_node;
    m_handler->m_node = font_node;

    // font attributes:

    // size
    float pointSize = -1.0f;
    bool hasSize = HasParam(wxT("size"));
    if (hasSize)
        pointSize = GetFloat(wxT("size"), -1.0f);

    // style
    wxFontStyle istyle = wxFONTSTYLE_NORMAL;
    bool hasStyle = HasParam(wxT("style"));
    if (hasStyle)
    {
        wxString style = GetParamValue(wxT("style"));
        if (style == wxT("italic"))
            istyle = wxFONTSTYLE_ITALIC;
        else if (style == wxT("slant"))
            istyle = wxFONTSTYLE_SLANT;
        else if (style != wxT("normal"))
        {
            ReportParamError
            (
                param,
                wxString::Format("unknown font style \"%s\"", style)
            );
        }
    }

    // weight
    long iweight = wxFONTWEIGHT_NORMAL;
    bool hasWeight = HasParam(wxT("weight"));
    if (hasWeight)
    {
        wxString weight = GetParamValue(wxT("weight"));
        if (weight.ToLong(&iweight))
        {
            if (iweight <= wxFONTWEIGHT_INVALID || iweight > wxFONTWEIGHT_MAX)
            {
                ReportParamError
                (
                    param,
                    wxString::Format("invalid font weight value \"%d\"", iweight)
                );
            }
        }
        else if (weight == wxT("thin"))
            iweight = wxFONTWEIGHT_THIN;
        else if (weight == wxT("extralight"))
            iweight = wxFONTWEIGHT_EXTRALIGHT;
        else if (weight == wxT("light"))
            iweight = wxFONTWEIGHT_LIGHT;
        else if (weight == wxT("medium"))
            iweight = wxFONTWEIGHT_MEDIUM;
        else if (weight == wxT("semibold"))
            iweight = wxFONTWEIGHT_SEMIBOLD;
        else if (weight == wxT("bold"))
            iweight = wxFONTWEIGHT_BOLD;
        else if (weight == wxT("extrabold"))
            iweight = wxFONTWEIGHT_EXTRABOLD;
        else if (weight == wxT("heavy"))
            iweight = wxFONTWEIGHT_HEAVY;
        else if (weight == wxT("extraheavy"))
            iweight = wxFONTWEIGHT_EXTRAHEAVY;
        else if (weight != wxT("normal"))
        {
            ReportParamError
            (
                param,
                wxString::Format("unknown font weight \"%s\"", weight)
            );
        }
    }

    // underline
    bool hasUnderlined = HasParam(wxT("underlined"));
    bool underlined = hasUnderlined ? GetBool(wxT("underlined"), false) : false;

    // strikethrough
    bool hasStrikethrough = HasParam(wxT("strikethrough"));
    bool strikethrough = hasStrikethrough ? GetBool(wxT("strikethrough"), false) : false;

    // family and facename
    wxFontFamily ifamily = wxFONTFAMILY_DEFAULT;
    bool hasFamily = HasParam(wxT("family"));
    if (hasFamily)
    {
        wxString family = GetParamValue(wxT("family"));
        if (family == wxT("default")) ifamily = wxFONTFAMILY_DEFAULT;
        else if (family == wxT("decorative")) ifamily = wxFONTFAMILY_DECORATIVE;
        else if (family == wxT("roman")) ifamily = wxFONTFAMILY_ROMAN;
        else if (family == wxT("script")) ifamily = wxFONTFAMILY_SCRIPT;
        else if (family == wxT("swiss")) ifamily = wxFONTFAMILY_SWISS;
        else if (family == wxT("modern")) ifamily = wxFONTFAMILY_MODERN;
        else if (family == wxT("teletype")) ifamily = wxFONTFAMILY_TELETYPE;
        else
        {
            ReportParamError
            (
                param,
                wxString::Format("unknown font family \"%s\"", family)
            );
        }
    }


    wxString facename;
    bool hasFacename = HasParam(wxT("face"));
    if (hasFacename)
    {
        wxString faces = GetParamValue(wxT("face"));
        wxStringTokenizer tk(faces, wxT(","));
#if wxUSE_FONTENUM
        wxArrayString facenames(wxFontEnumerator::GetFacenames());
        while (tk.HasMoreTokens())
        {
            int index = facenames.Index(tk.GetNextToken(), false);
            if (index != wxNOT_FOUND)
            {
                facename = facenames[index];
                break;
            }
        }
#else // !wxUSE_FONTENUM
        // just use the first face name if we can't check its availability:
        if (tk.HasMoreTokens())
            facename = tk.GetNextToken();
#endif // wxUSE_FONTENUM/!wxUSE_FONTENUM
    }

    // encoding
    wxFontEncoding enc = wxFONTENCODING_DEFAULT;
    bool hasEncoding = HasParam(wxT("encoding"));
#if wxUSE_FONTMAP
    if (hasEncoding)
    {
        wxString encoding = GetParamValue(wxT("encoding"));
        wxFontMapper mapper;
        if (!encoding.empty())
            enc = mapper.CharsetToEncoding(encoding);
        if (enc == wxFONTENCODING_SYSTEM)
            enc = wxFONTENCODING_DEFAULT;
    }
#endif // wxUSE_FONTMAP

    wxFont font;

    // is this font based on a system font?
    if (HasParam(wxT("sysfont")))
    {
        font = GetSystemFont(GetParamValue(wxT("sysfont")));
        if (HasParam(wxT("inherit")))
        {
            ReportParamError
            (
                param,
                "double specification of \"sysfont\" and \"inherit\""
            );
        }
    }
    // or should the font of the widget be used?
    else if (GetBool(wxT("inherit"), false))
    {
        if (parent)
            font = parent->GetFont();
        else
        {
            ReportParamError
            (
                param,
                "no parent window specified to derive the font from"
            );
        }
    }

    if (font.IsOk())
    {
        if (pointSize > 0)
        {
            font.SetFractionalPointSize(double(pointSize));
            if (HasParam(wxT("relativesize")))
            {
                ReportParamError
                (
                    param,
                    "double specification of \"size\" and \"relativesize\""
                );
            }
        }
        else if (HasParam(wxT("relativesize")))
            font.SetPointSize(int(font.GetPointSize() *
                                     GetFloat(wxT("relativesize"))));

        if (hasStyle)
            font.SetStyle(istyle);
        if (hasWeight)
            font.SetNumericWeight(iweight);
        if (hasUnderlined)
            font.SetUnderlined(underlined);
        if (hasStrikethrough)
            font.SetStrikethrough(strikethrough);
        if (hasFamily)
            font.SetFamily(ifamily);
        if (hasFacename)
            font.SetFaceName(facename);
        if (hasEncoding)
            font.SetDefaultEncoding(enc);
    }
    else // not based on system font
    {
        font = wxFontInfo(double(pointSize))
                .FaceName(facename)
                .Family(ifamily)
                .Style(istyle)
                .Weight(iweight)
                .Underlined(underlined)
                .Strikethrough(strikethrough)
                .Encoding(enc)
                ;
    }

    m_handler->m_node = oldnode;
    return font;
}


void wxXmlResourceHandlerImpl::SetupWindow(wxWindow *wnd)
{
    // This is called immediately after creating a window, so it's a convenient
    // place to check that it was created successfully without duplicating this
    // check in all handlers.
    if ( !wnd->GetHandle() )
    {
        wxLogError(_("Creating %s \"%s\" failed."),
                   m_handler->m_class, GetName());
        return;
    }

    //FIXME : add cursor

    const wxString variant = GetParamValue(wxS("variant"));
    if (!variant.empty())
    {
        if (variant == wxS("normal"))
            wnd->SetWindowVariant(wxWINDOW_VARIANT_NORMAL);
        else if (variant == wxS("small"))
            wnd->SetWindowVariant(wxWINDOW_VARIANT_SMALL);
        else if (variant == wxS("mini"))
            wnd->SetWindowVariant(wxWINDOW_VARIANT_MINI);
        else if (variant == wxS("large"))
            wnd->SetWindowVariant(wxWINDOW_VARIANT_LARGE);
        else
        {
            ReportParamError
            (
                wxS("variant"),
                wxString::Format
                (
                    "Invalid window variant \"%s\": must be one of "
                    "normal|small|mini|large.",
                    variant
                )
            );
        }
    }
    if (HasParam(wxT("exstyle")))
        // Have to OR it with existing style, since
        // some implementations (e.g. wxGTK) use the extra style
        // during creation
        wnd->SetExtraStyle(wnd->GetExtraStyle() | GetStyle(wxT("exstyle")));
    if (HasParam(wxT("bg")))
        wnd->SetBackgroundColour(GetColour(wxT("bg")));
    if (HasParam(wxT("ownbg")))
        wnd->SetOwnBackgroundColour(GetColour(wxT("ownbg")));
    if (HasParam(wxT("fg")))
        wnd->SetForegroundColour(GetColour(wxT("fg")));
    if (HasParam(wxT("ownfg")))
        wnd->SetOwnForegroundColour(GetColour(wxT("ownfg")));
    if (GetBool(wxT("enabled"), 1) == 0)
        wnd->Enable(false);
    if (GetBool(wxT("focused"), 0) == 1)
        wnd->SetFocus();
#if wxUSE_TOOLTIPS
    if (HasParam(wxT("tooltip")))
        wnd->SetToolTip(GetNodeText(GetParamNode(wxT("tooltip"))));
#endif
    if (HasParam(wxT("font")))
        wnd->SetFont(GetFont(wxT("font"), wnd));
    if (HasParam(wxT("ownfont")))
        wnd->SetOwnFont(GetFont(wxT("ownfont"), wnd));
    if (HasParam(wxT("help")))
        wnd->SetHelpText(GetNodeText(GetParamNode(wxT("help"))));
}


void wxXmlResourceHandlerImpl::CreateChildren(wxObject *parent, bool this_hnd_only)
{
    for ( wxXmlNode *n = m_handler->m_node->GetChildren(); n; n = n->GetNext() )
    {
        if ( IsObjectNode(n) )
        {
            m_handler->m_resource->DoCreateResFromNode(*n, parent, nullptr,
                                            this_hnd_only ? this->GetHandler() : nullptr);
       }
    }
}


void wxXmlResourceHandlerImpl::CreateChildrenPrivately(wxObject *parent, wxXmlNode *rootnode)
{
    wxXmlNode *root;
    if (rootnode == nullptr) root = m_handler->m_node; else root = rootnode;
    wxXmlNode *n = root->GetChildren();

    while (n)
    {
        if (n->GetType() == wxXML_ELEMENT_NODE && GetHandler()->CanHandle(n))
        {
            CreateResource(n, parent, nullptr);
        }
        n = n->GetNext();
    }
}


//-----------------------------------------------------------------------------
// errors reporting
//-----------------------------------------------------------------------------

void wxXmlResourceHandlerImpl::ReportError(const wxString& message)
{
    m_handler->m_resource->ReportError(m_handler->m_node, message);
}

void wxXmlResourceHandlerImpl::ReportError(wxXmlNode *context,
                                       const wxString& message)
{
    m_handler->m_resource->ReportError(context ? context : m_handler->m_node, message);
}

void wxXmlResourceHandlerImpl::ReportParamError(const wxString& param,
                                            const wxString& message)
{
    m_handler->m_resource->ReportError(GetParamNode(param), message);
}

void wxXmlResource::ReportError(const wxXmlNode *context, const wxString& message)
{
    if ( !context )
    {
        DoReportError(wxString(), nullptr, message);
        return;
    }

    // We need to find out the file that 'context' is part of. Performance of
    // this code is not critical, so we simply find the root XML node and
    // compare it with all loaded XRC files.
    const wxString filename = GetFileNameFromNode(context, Data());

    DoReportError(filename, context, message);
}

void wxXmlResource::DoReportError(const wxString& xrcFile, const wxXmlNode *position,
                                  const wxString& message)
{
    const int line = position ? position->GetLineNumber() : -1;

    wxString loc;
    if ( !xrcFile.empty() )
        loc = xrcFile + ':';
    if ( line != -1 )
        loc += wxString::Format("%d:", line);
    if ( !loc.empty() )
        loc += ' ';

    wxLogError("XRC error: %s%s", loc, message);
}


//-----------------------------------------------------------------------------
// XRCID implementation
//-----------------------------------------------------------------------------

#define XRCID_TABLE_SIZE     1024


struct XRCID_record
{
    /* Hold the id so that once an id is allocated for a name, it
       does not get created again by NewControlId at least
       until we are done with it */
    wxWindowIDRef id;
    char *key;
    XRCID_record *next;
};

static XRCID_record *XRCID_Records[XRCID_TABLE_SIZE] = {nullptr};

// Extremely simplistic hash function which probably ought to be replaced with
// wxStringHash::stringHash().
static inline unsigned XRCIdHash(const char *str_id)
{
    unsigned index = 0;

    for (const char *c = str_id; *c != '\0'; c++) index += (unsigned int)*c;
    index %= XRCID_TABLE_SIZE;

    return index;
}

static void XRCID_Assign(const wxString& str_id, int value)
{
    const wxCharBuffer buf_id(str_id.mb_str());
    const unsigned index = XRCIdHash(buf_id);


    XRCID_record *oldrec = nullptr;
    for (XRCID_record *rec = XRCID_Records[index]; rec; rec = rec->next)
    {
        if (wxStrcmp(rec->key, buf_id) == 0)
        {
            rec->id = value;
            return;
        }
        oldrec = rec;
    }

    XRCID_record **rec_var = (oldrec == nullptr) ?
                              &XRCID_Records[index] : &oldrec->next;
    *rec_var = new XRCID_record;
    (*rec_var)->key = wxStrdup(str_id);
    (*rec_var)->id = value;
    (*rec_var)->next = nullptr;
}

static int XRCID_Lookup(const char *str_id, int value_if_not_found = wxID_NONE)
{
    const unsigned index = XRCIdHash(str_id);


    XRCID_record *oldrec = nullptr;
    for (XRCID_record *rec = XRCID_Records[index]; rec; rec = rec->next)
    {
        if (wxStrcmp(rec->key, str_id) == 0)
        {
            return rec->id;
        }
        oldrec = rec;
    }

    XRCID_record **rec_var = (oldrec == nullptr) ?
                              &XRCID_Records[index] : &oldrec->next;
    *rec_var = new XRCID_record;
    (*rec_var)->key = wxStrdup(str_id);
    (*rec_var)->next = nullptr;

    char *end;
    if (value_if_not_found != wxID_NONE)
        (*rec_var)->id = value_if_not_found;
    else
    {
        int asint = wxStrtol(str_id, &end, 10);
        if (*str_id && *end == 0)
        {
            // if str_id was integer, keep it verbosely:
            (*rec_var)->id = asint;
        }
        else
        {
            (*rec_var)->id = wxWindowBase::NewControlId();
        }
    }

    return (*rec_var)->id;
}

namespace
{

// flag indicating whether standard XRC ids were already initialized
static bool gs_stdIDsAdded = false;

void AddStdXRCID_Records()
{
#define stdID(id) XRCID_Lookup(#id, id)
    stdID(-1);

    stdID(wxID_ANY);
    stdID(wxID_SEPARATOR);

    stdID(wxID_OPEN);
    stdID(wxID_CLOSE);
    stdID(wxID_NEW);
    stdID(wxID_SAVE);
    stdID(wxID_SAVEAS);
    stdID(wxID_REVERT);
    stdID(wxID_EXIT);
    stdID(wxID_UNDO);
    stdID(wxID_REDO);
    stdID(wxID_HELP);
    stdID(wxID_PRINT);
    stdID(wxID_PRINT_SETUP);
    stdID(wxID_PAGE_SETUP);
    stdID(wxID_PREVIEW);
    stdID(wxID_ABOUT);
    stdID(wxID_HELP_CONTENTS);
    stdID(wxID_HELP_INDEX);
    stdID(wxID_HELP_SEARCH);
    stdID(wxID_HELP_COMMANDS);
    stdID(wxID_HELP_PROCEDURES);
    stdID(wxID_HELP_CONTEXT);
    stdID(wxID_CLOSE_ALL);
    stdID(wxID_PREFERENCES);

    stdID(wxID_EDIT);
    stdID(wxID_CUT);
    stdID(wxID_COPY);
    stdID(wxID_PASTE);
    stdID(wxID_CLEAR);
    stdID(wxID_FIND);
    stdID(wxID_DUPLICATE);
    stdID(wxID_SELECTALL);
    stdID(wxID_DELETE);
    stdID(wxID_REPLACE);
    stdID(wxID_REPLACE_ALL);
    stdID(wxID_PROPERTIES);

    stdID(wxID_VIEW_DETAILS);
    stdID(wxID_VIEW_LARGEICONS);
    stdID(wxID_VIEW_SMALLICONS);
    stdID(wxID_VIEW_LIST);
    stdID(wxID_VIEW_SORTDATE);
    stdID(wxID_VIEW_SORTNAME);
    stdID(wxID_VIEW_SORTSIZE);
    stdID(wxID_VIEW_SORTTYPE);


    stdID(wxID_FILE1);
    stdID(wxID_FILE2);
    stdID(wxID_FILE3);
    stdID(wxID_FILE4);
    stdID(wxID_FILE5);
    stdID(wxID_FILE6);
    stdID(wxID_FILE7);
    stdID(wxID_FILE8);
    stdID(wxID_FILE9);


    stdID(wxID_OK);
    stdID(wxID_CANCEL);
    stdID(wxID_APPLY);
    stdID(wxID_YES);
    stdID(wxID_NO);
    stdID(wxID_STATIC);
    stdID(wxID_FORWARD);
    stdID(wxID_BACKWARD);
    stdID(wxID_DEFAULT);
    stdID(wxID_MORE);
    stdID(wxID_SETUP);
    stdID(wxID_RESET);
    stdID(wxID_CONTEXT_HELP);
    stdID(wxID_YESTOALL);
    stdID(wxID_NOTOALL);
    stdID(wxID_ABORT);
    stdID(wxID_RETRY);
    stdID(wxID_IGNORE);
    stdID(wxID_ADD);
    stdID(wxID_REMOVE);

    stdID(wxID_UP);
    stdID(wxID_DOWN);
    stdID(wxID_HOME);
    stdID(wxID_REFRESH);
    stdID(wxID_STOP);
    stdID(wxID_INDEX);

    stdID(wxID_BOLD);
    stdID(wxID_ITALIC);
    stdID(wxID_JUSTIFY_CENTER);
    stdID(wxID_JUSTIFY_FILL);
    stdID(wxID_JUSTIFY_RIGHT);
    stdID(wxID_JUSTIFY_LEFT);
    stdID(wxID_UNDERLINE);
    stdID(wxID_INDENT);
    stdID(wxID_UNINDENT);
    stdID(wxID_ZOOM_100);
    stdID(wxID_ZOOM_FIT);
    stdID(wxID_ZOOM_IN);
    stdID(wxID_ZOOM_OUT);
    stdID(wxID_UNDELETE);
    stdID(wxID_REVERT_TO_SAVED);
    stdID(wxID_CDROM);
    stdID(wxID_CONVERT);
    stdID(wxID_EXECUTE);
    stdID(wxID_FLOPPY);
    stdID(wxID_HARDDISK);
    stdID(wxID_BOTTOM);
    stdID(wxID_FIRST);
    stdID(wxID_LAST);
    stdID(wxID_TOP);
    stdID(wxID_INFO);
    stdID(wxID_JUMP_TO);
    stdID(wxID_NETWORK);
    stdID(wxID_SELECT_COLOR);
    stdID(wxID_SELECT_FONT);
    stdID(wxID_SORT_ASCENDING);
    stdID(wxID_SORT_DESCENDING);
    stdID(wxID_SPELL_CHECK);
    stdID(wxID_STRIKETHROUGH);


    stdID(wxID_SYSTEM_MENU);
    stdID(wxID_CLOSE_FRAME);
    stdID(wxID_MOVE_FRAME);
    stdID(wxID_RESIZE_FRAME);
    stdID(wxID_MAXIMIZE_FRAME);
    stdID(wxID_ICONIZE_FRAME);
    stdID(wxID_RESTORE_FRAME);



    stdID(wxID_MDI_WINDOW_CASCADE);
    stdID(wxID_MDI_WINDOW_TILE_HORZ);
    stdID(wxID_MDI_WINDOW_TILE_VERT);
    stdID(wxID_MDI_WINDOW_ARRANGE_ICONS);
    stdID(wxID_MDI_WINDOW_PREV);
    stdID(wxID_MDI_WINDOW_NEXT);
#undef stdID
}

} // anonymous namespace


/*static*/
int wxXmlResource::DoGetXRCID(const char *str_id, int value_if_not_found)
{
    if ( !gs_stdIDsAdded )
    {
        gs_stdIDsAdded = true;
        AddStdXRCID_Records();
    }

    return XRCID_Lookup(str_id, value_if_not_found);
}

/* static */
wxString wxXmlResource::FindXRCIDById(int numId)
{
    for ( int i = 0; i < XRCID_TABLE_SIZE; i++ )
    {
        for ( XRCID_record *rec = XRCID_Records[i]; rec; rec = rec->next )
        {
            if ( rec->id == numId )
                return wxString(rec->key);
        }
    }

    return wxString();
}

static void CleanXRCID_Record(XRCID_record *rec)
{
    if (rec)
    {
        CleanXRCID_Record(rec->next);

        free(rec->key);
        delete rec;
    }
}

static void CleanXRCID_Records()
{
    for (int i = 0; i < XRCID_TABLE_SIZE; i++)
    {
        CleanXRCID_Record(XRCID_Records[i]);
        XRCID_Records[i] = nullptr;
    }

    gs_stdIDsAdded = false;
}


//-----------------------------------------------------------------------------
// module and globals
//-----------------------------------------------------------------------------

// normally we would do the cleanup from wxXmlResourceModule::OnExit() but it
// can happen that some XRC records have been created because of the use of
// XRCID() in event tables, which happens during static objects initialization,
// but then the application initialization failed and so the wx modules were
// neither initialized nor cleaned up -- this static object does the cleanup in
// this case
static struct wxXRCStaticCleanup
{
    ~wxXRCStaticCleanup() { CleanXRCID_Records(); }
} s_staticCleanup;

class wxXmlResourceModule: public wxModule
{
    wxDECLARE_DYNAMIC_CLASS(wxXmlResourceModule);
public:
    wxXmlResourceModule() {}
    bool OnInit() override
    {
        wxXmlResource::AddSubclassFactory(new wxXmlSubclassFactoryCXX);
        return true;
    }
    void OnExit() override
    {
        delete wxXmlResource::Set(nullptr);
        delete wxIdRangeManager::Set(nullptr);
        wxXmlResourceInternal::ms_subclassFactories.clear();
        CleanXRCID_Records();
    }
};

wxIMPLEMENT_DYNAMIC_CLASS(wxXmlResourceModule, wxModule);


// When wxXml is loaded dynamically after the application is already running
// then the built-in module system won't pick this one up.  Add it manually.
void wxXmlInitResourceModule()
{
    wxModule* module = new wxXmlResourceModule;
    wxModule::RegisterModule(module);
    wxModule::InitializeModules();
}

#endif // wxUSE_XRC
