Creating progress bar for wxXmlDocument loading

Our XMLWizard program can load large XML files. For this, we are using wxXmlDocument class from wxWidgets, which has a DOM parser underneath. Large files can take a long time to load, so we needed to add a progress bar to show progress to the user. Since there was nothing built in, I decided to take advantage of ability to load data via file input stream. I sub-classed wxFileInputStream and added a couple of hooks to detect which part of XML file is being read by wxXmlDocument class.

Once data is loaded into XMLWizard, it needs to be processed. Once again, huge file can take a lot of time, and counting number of nodes in XML document by iterating the nodes can also be very slow. So I added a quick&dirty XML open tag counter that gives us some approximation of number of nodes in the file by counting the occurence of < character. It only counts those not followed by a slash, so that <open> tags are only counted, and </close> are not.

Here's the complete code for the sub-class, plus some sample code to demonstrate usage:

//-----------------------------------------------------------------------------
class MonitoredStream: public wxFileInputStream
{
private:
    wxProgressDialog *f;
    size_t maksM, sz, last;
    size_t ltcntM;
    bool canceledM;
public:
    MonitoredStream(const wxString& filename)
        :wxFileInputStream(filename), f(0), maksM(0), sz(0), last(0),
        ltcntM(0), canceledM(false)
    {
    };

    void setProgress(wxProgressDialog *dialog, size_t maks)
    {
        f = dialog;
        maksM = maks;
    }

    bool isCanceled()
    {
        return canceledM;
    }

    size_t getApproxNumberOfTags()
    {
        return ltcntM;
    }

    size_t OnSysRead(void *buffer, size_t size)
    {
        sz += size;
        if (sz - last > 10240)  // update every 10k
        {
            last = sz;
            if (sz > maksM)
                sz = maksM;
            if (!f->Update(sz))
            {
                m_lasterror = wxSTREAM_READ_ERROR;
                canceledM = true;
                return 0;
            }
        }
        size_t szread = wxFileInputStream::OnSysRead(buffer, size);
        if (szread <= size)
        {
            char *cp = (char *)buffer;
            for (size_t i = 0; i < szread; i++, ++cp)
                if (*cp == '<' && (i > szread-2 || *(cp+1) != '/'))
                    ltcntM++;
        }
        return szread;
    }
};
//-----------------------------------------------------------------------------
// example usage function
bool loadXMLtree(const wxString& filename)
{
    wxFileName fn(filename);
    bool ok = false;
    size_t tags = 0;
    {   // we destroy progress dialog when done
        MonitoredStream is(filename);
        if (!is.IsOk())
        {
            wxMessageBox(_("Cannot read input file"), _("File error"),
                wxOK | wxICON_ERROR, this);
            return false;
        }
        is.SeekI(0, wxFromEnd);

        wxProgressDialog pd(_("Loading XML file"),
            _("Loading XML data from ") + fn.GetFullName(),
            is.TellI(), 0,
            wxPD_APP_MODAL|wxPD_ELAPSED_TIME|wxPD_AUTO_HIDE|wxPD_CAN_ABORT);
        is.setProgress(&pd, is.TellI());
        is.SeekI(0);    // rewind
        ok = docM.Load(is);
        if (!ok && is.isCanceled())
            return false;
        tags = is.getApproxNumberOfTags();
    }
    return true;
}
//-----------------------------------------------------------------------------

This code is released into public domain. Use it as you wish. You may thank me by mentioning guacosoft.com on Twitter or Google+.