/////////////////////////////////////////////////////////////////////////////
// Name:        wxp.cpp
// Purpose:     wxPackageXML, wxPresetXMLDescriptor, wxIfNodeListManager,
//              wxPackageRepositoryXMLDescriptor
// Author:      Francesco Montorsi
// Modified by:
// Created:     2006-06-29
// RCS-ID:      $Id: wxp.cpp,v 1.8 2007/01/31 13:27:46 frm Exp $
// Copyright:   (c) Francesco Montorsi
// Licence:     wxWidgets licence
/////////////////////////////////////////////////////////////////////////////


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

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

// includes
#include <wx/fileconf.h>
#include <wx/mstream.h>
#include <wx/apptrait.h>
#include <wx/zstream.h>

#if wxUSE_GUI
    #include <wx/progdlg.h>
#endif

#include "wxp/wxp.h"
#include "wxp/packagewxp.h"


// RTTI
IMPLEMENT_DYNAMIC_CLASS(wxPackageXML, wxXmlDocument)
IMPLEMENT_DYNAMIC_CLASS(wxPresetXMLDescriptor, wxPackageXML)
IMPLEMENT_DYNAMIC_CLASS(wxPackageRepositoryXMLDescriptor, wxXmlDocument)

// global objects
wxPackageCommandOptionArrayHashMap wxPresetXMLDescriptor::s_hashmapOptions;
wxPackageCommandSetHashMap wxPresetXMLDescriptor::s_hashmapCommands;



// ----------------------------------------------------------------------------
// wxPackageXML
// ----------------------------------------------------------------------------

void wxPackageXML::HandleUnknownTag(wxXmlNode *node)
{
    if (node->GetType() != wxXML_COMMENT_NODE &&
        (node->GetType() != wxXML_TEXT_NODE || !node->IsWhitespaceOnly()))
    {
        wxLogWarning(wxT("Discarding unknown/misplaced tag <%s> found in <%s>"),
                     node->GetName().c_str(), node->GetParent()->GetName().c_str());

        // remove it so that later, when eventually this document will be saved,
        // this error won't be saved, too
        node->GetParent()->RemoveChild(node);
    }
}

wxString wxPackageXML::GetStrippedNodeContent(const wxXmlNode *n) const
{
    // IMPORTANT: do not do any substitution otherwise we would be unable
    //            to save back this node with the $(...) keys preserved!
    //  wxString str = wxPackageInfo::SubstituteStandard(n->GetNodeContent());
    wxString str = n->GetNodeContent();

    // we never want leading/trailing whitespaces!
    return str.Strip(wxString::both);
}

wxPackageCondition wxPackageXML::LoadCondition(const wxXmlNode *n)
{
    wxString buildsys(n->GetPropVal(wxT("buildsys"), wxEmptyString));
    wxString formats(n->GetPropVal(wxT("formats"), wxEmptyString));
    wxString platforms(n->GetPropVal(wxT("platforms"), wxEmptyString));
    wxString stages(n->GetPropVal(wxT("stages"), wxEmptyString));

    // strings -> lists
    long b = wxString2PackageBuildSystemTypeList(buildsys);
    long f = wxString2PackageCompilerFormatList(formats);
    long p = wxString2PortIdList(platforms);
    long s = wxString2PackageBuildSystemStageList(stages);

    // if no build systems were listed, we consider
    // that all build systems satisfy this condition;
    // the same for formats,platforms,stages
    if (b == 0) b = wxGetFullPackageBuildSystemTypeList();
    if (f == 0) f = wxGetFullPackageCompilerFormatList();
    if (p == 0) p = wxGetFullPortIdList();
    if (s == 0) s = wxGetFullPackageBuildSystemStageList();

    return wxPackageCondition(p, f, b, s);
}

wxPackageCommandOptionArray wxPackageXML::LoadOptions(const wxXmlNode *n)
{
    // this function is used to load both <option-set> and <options> tags
    if (n->GetName() != wxT("option-set") &&
        n->GetName() != wxT("options"))
        return wxEmptyPackageCommandOptionArray;

    wxPackageCommandOptionArray ret;
    wxXmlNode *cond = n->GetChildren();
    while (cond)
    {
        if (cond->GetName() == wxT("if"))
        {
            wxPackageCondition c = LoadCondition(cond);

            // load this IF block even if the running platform does not coincide
            // with the platform specified in the <if> block.
            // this is because we must be able to save the wxPackageCommandOptionArray
            // we are building, later and without any data loss
            wxXmlNode *optnode = cond->GetChildren();
            while (optnode)
            {
                if (optnode->GetName() == wxT("option"))
                {
                    wxPackageCommandOption opt = LoadOption(optnode);
                    opt.SetCondition(c);

                    // duplicate options will be automatically discarded by
                    // wxPackageCommandOptionArray::Add
                    if (opt.IsOk())
                        ret.Add(opt);
                    else
                        wxLogWarning(wxT("Discarding invalid option named '%s'"),
                                     opt.GetName().c_str());
                }
                else
                    HandleUnknownTag(optnode);

                optnode = optnode->GetNext();
            }
        }
        else if (cond->GetName() == wxT("option"))
        {
            // a non-conditional option!

            wxPackageCommandOption opt = LoadOption(cond);
            if (opt.IsOk())
                ret.Add(opt);
            else
                wxLogWarning(wxT("Discarding invalid option named '%s'"), 
                             opt.GetName().c_str());
        }
        else
            HandleUnknownTag(cond);

        // proceed
        cond = cond->GetNext();
    }

    return ret;
}

wxPackageCommandOption wxPackageXML::LoadOption(const wxXmlNode *n)
{
    if (n->GetName() != wxT("option") ||
        !n->HasProp(wxT("name")))
        return wxEmptyPackageCommandOption;

    wxString name(n->GetPropVal(wxT("name"), wxEmptyString));
    wxString category(n->GetPropVal(wxT("category"), wxEmptyString));
    wxString defvalue, description, group, values, valuesdesc, value;

    wxXmlNode *child = n->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("default-value"))
        {
            defvalue = GetStrippedNodeContent(child);
        }
        else if (child->GetName() == wxT("description"))
        {
            description = GetStrippedNodeContent(child);
            description.Trim(false);
            description.Trim(true);
        }
        else if (child->GetName() == wxT("group"))
        {
            group = GetStrippedNodeContent(child);
        }
        else if (child->GetName() == wxT("values"))
        {
            values = GetStrippedNodeContent(child);
        }
        else if (child->GetName() == wxT("values-description"))
        {
            valuesdesc = GetStrippedNodeContent(child);
        }
        else if (child->GetName() == wxT("value"))
        {
            // this node is 'special': it's not in bakefile <option> syntax
            // (like also 'group') but, more important, it can be
            // used to specify the current value of the option which can be
            // different from the default value.
            value = GetStrippedNodeContent(child);
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    // fill in the holes
    if (group == wxEmptyString)
        group = wxT("Ungrouped");
    if (value == wxEmptyString)
        value = defvalue;

    // we have now gathered all we need for building this option
    wxPackageCommandOptionCategory cat = wxString2PackageCommandOptionCategory(category);
    wxPackageCommandOption opt(name, values, value, defvalue, valuesdesc,
                            description, group, cat);

    // this can be done only after the option has been created
    if (cat == wxPCOC_INVALID && !n->HasProp(wxT("category")))
        opt.AutodetectCategory();   // try some smart heuristic

    // by default an option can always be used.
    // if this option is inside an <if> tag the caller will adjust the
    // condition associated with opt:
    opt.GetCondition().SetAlwaysTrue();

    return opt;
}

wxPackageCommand wxPackageXML::LoadCommand(const wxXmlNode *n)
{
    wxPackageCommand ret(GetStrippedNodeContent(n),
                        n->GetPropVal(wxT("path"), wxEmptyString));

    // by default a command can always be used.
    // if this option is inside an <if> tag the caller will adjust the
    // condition associated with opt:
    ret.GetCondition().SetAlwaysTrue();
    return ret;
}

wxXmlNode *wxPackageXML::AddChildIndentation(wxXmlNode *node, int level)
{
    wxASSERT(node);
    if (m_nIndentationStep < 0)
        return node->GetChildren();

    wxXmlNode *indent = new wxXmlNode(wxXML_TEXT_NODE, wxEmptyString,
                                      wxT("\n") + wxString(wxT(' '), 
                                      level*m_nIndentationStep));
    node->AddChild(indent);
    return indent;
}

wxXmlNode *wxPackageXML::InsertChildIndentation(wxXmlNode *node, 
                                                wxXmlNode *before_node, 
                                                int level)
{    
    wxASSERT(node);
    if (m_nIndentationStep < 0)
        return node->GetChildren();

    wxXmlNode *indent = new wxXmlNode(wxXML_TEXT_NODE, wxEmptyString,
                                      wxT("\n") + wxString(wxT(' '), 
                                      level*m_nIndentationStep));
    node->InsertChild(indent, before_node);
    return indent;
}

wxXmlNode *wxPackageXML::AddSiblingIndentation(wxXmlNode *node, int level)
{
    wxASSERT(node);
    if (m_nIndentationStep < 0)
        return node;

    wxXmlNode *indent = new wxXmlNode(wxXML_TEXT_NODE, wxEmptyString,
                                      wxT("\n") + wxString(wxT(' '), 
                                      level*m_nIndentationStep));
    indent->SetParent(node->GetParent());

    wxXmlNode *oldnext = node->GetNext();
    node->SetNext(indent);
    indent->SetNext(oldnext);
    return indent;
}

wxXmlNode *wxPackageXML::SaveCondition(const wxPackageCondition &cond, int WXUNUSED(level))
{
    // NOTE: indentation level is unused since the <if> node does
    //       not need any children subnode - at least at this point
    //       of XML tree creation

    wxXmlNode *ret = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("if"));

    wxString fmt,plat,bs,stages;

    if (cond.GetFormats() != wxGetFullPackageCompilerFormatList())
        fmt = cond.GetFormatsStr();
    if (cond.GetPlatforms() != wxGetFullPortIdList())
        plat = cond.GetPlatformsStr();
    if (cond.GetBuildSystems() != wxGetFullPackageBuildSystemTypeList())
        bs = cond.GetBuildSystemsStr();
    if (cond.GetStages() != wxGetFullPackageBuildSystemStageList())
        stages = cond.GetStagesStr();

    if (!fmt.IsEmpty()) ret->AddProperty(wxT("formats"), fmt.Lower());
    if (!plat.IsEmpty()) ret->AddProperty(wxT("platforms"), plat.Lower());
    if (!bs.IsEmpty()) ret->AddProperty(wxT("buildsys"), bs.Lower());
    if (!stages.IsEmpty()) ret->AddProperty(wxT("stages"), stages.Lower());

    return ret;
}

wxXmlNode *wxPackageXML::SaveOption(const wxPackageCommandOption &opt, int level)
{
#define wxADD_CHILD_WITH_TEXT(name, text)                               \
    if (!text.IsEmpty())                                                \
    {                                                                   \
        wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, name);      \
        node->AddChild(new wxXmlNode(wxXML_TEXT_NODE, name, text));     \
        AddChildIndentation(optnode, level+1);                          \
        optnode->AddChild(node);                                        \
    }

    wxXmlNode *optnode = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("option"));
    optnode->AddProperty(wxT("name"), opt.GetName());

    if (opt.GetCategory() != wxPCOC_LISTED_VALUES)
        optnode->AddProperty(wxT("category"),
                             wxPackageCommandOptionCategory2String(opt.GetCategory()));

    // save value only if different from default value
    if (opt.IsChanged())
        wxADD_CHILD_WITH_TEXT(wxT("value"), opt.GetValue());

    wxADD_CHILD_WITH_TEXT(wxT("default-value"), opt.GetDefaultValue());
    wxADD_CHILD_WITH_TEXT(wxT("values"), opt.GetAllowedValues());
    wxADD_CHILD_WITH_TEXT(wxT("values-description"), opt.GetValueDesc());
    wxADD_CHILD_WITH_TEXT(wxT("description"), opt.GetDescription());
    wxADD_CHILD_WITH_TEXT(wxT("group"), opt.GetGroup());
    AddChildIndentation(optnode, level);

    return optnode;
}

wxXmlNode *wxPackageXML::SaveCommand(const wxPackageCommand &cmd, int WXUNUSED(level))
{
    // NOTE: indentation level is unused since the <cmd> node does
    //       not need any children subnode

    // create command node
    wxXmlNode *cmdnode = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("cmd"));
    cmdnode->AddChild(new wxXmlNode(wxXML_TEXT_NODE, wxT("cmd"), cmd.GetCommand()));

    if (!cmd.GetWorkingPath().IsEmpty())
        cmdnode->AddProperty(wxT("path"), cmd.GetWorkingPath());
    return cmdnode;
}





// ----------------------------------------------------------------------------
// wxIfNodeListManager
// ----------------------------------------------------------------------------

void wxIfNodeListManager::SmartAppend(wxXmlNode *n, const wxPackageCondition &cnd, int level)
{
    if (m_previousif == NULL)
    {
        // previous node did not use an <if> block
        if (cnd != wxEmptyPackageCondition && !cnd.IsAlwaysTrue())
        {
            // this node needs an <if> block...
            m_previouscond = cnd;
            m_previousif = m_doc->SaveCondition(cnd, level);

            // add given node to this new <if> child
            m_previousif->AddChild(n);

            // indent the <if> child and add it to the parent
            m_doc->AddChildIndentation(m_parent, level);
            m_parent->AddChild(m_previousif);
        }
        else
        {
            // also this node does not need an <if> block...
            m_doc->AddChildIndentation(m_parent, level);
            m_parent->AddChild(n);

            // let m_previousif to NULL
            wxASSERT(m_previouscond == wxEmptyPackageCondition);
        }
    }
    else
    {
        // previous node used an <if> block
        if (cnd != wxEmptyPackageCondition && !cnd.IsAlwaysTrue())
        {
            if (cnd != m_previouscond)
            {
                // cannot reuse previous <if> !
                m_previouscond = cnd;
                m_previousif = m_doc->SaveCondition(cnd, level);

                // indent the <if> child:
                m_doc->AddChildIndentation(m_previousif, level+1);
                m_previousif->AddChild(n);
                m_doc->AddChildIndentation(m_previousif, level+1);

                m_parent->AddChild(m_previousif);
            }
            else
            {
                // previous <if> can be reused!
                m_doc->AddChildIndentation(m_previousif, level+1);
                m_previousif->AddChild(n);
                m_doc->AddChildIndentation(m_previousif, level+1);

                m_previouscond = cnd;
            }
        }
        else
        {
            // this node does not need an <if> block...
            m_doc->AddChildIndentation(m_previousif, level);
            m_parent->AddChild(n);
            m_doc->AddChildIndentation(m_previousif, level);

            m_previousif = NULL;
            m_previouscond = wxEmptyPackageCondition;
        }
    }
}

void wxIfNodeListManager::SaveOptions(const wxPackageCommandOptionArray &arr, int level)
{
    for (size_t i=0; i < arr.GetCount(); i++)
    {
        // wxIfNodeListManager will concatenate the <option> node
        // to the previous <if>, if possible
        wxXmlNode *newnode = m_doc->SaveOption(arr[i], level);
        SmartAppend(newnode, arr[i].GetCondition(), level);
    }
}

void wxIfNodeListManager::SaveCommands(const wxPackageCommandArray &arr, int level)
{
    for (size_t i=0; i < arr.GetCount(); i++)
    {
        // wxIfNodeListManager will concatenate the <cmd> node
        // to the previous <if>, if possible
        wxXmlNode *newnode = m_doc->SaveCommand(arr[i], level);
        SmartAppend(newnode, arr[i].GetCondition(), level);
    }
}




// ----------------------------------------------------------------------------
// wxPresetXMLDescriptor
// ----------------------------------------------------------------------------

#ifdef __WXDEBUG__
// static
void wxPresetXMLDescriptor::Dump()
{
    // dump loaded option sets
    wxPackageCommandOptionArrayHashMap::iterator it;
    for( it = s_hashmapOptions.begin(); it != s_hashmapOptions.end(); ++it )
    {
        wxString key = it->first;
        wxPackageCommandOptionArray &value = it->second;

        for (size_t i=0; i<value.GetCount(); i++)
            wxLogDebug(wxT("Optionset [%s] - option '%s' - default value '%s'"),
                    key.c_str(), value[i].GetName().c_str(),
                    value[i].GetDefaultValue().c_str());
    }

    // dump loaded command sets
    wxPackageCommandSetHashMap::iterator it2;
    for( it2 = s_hashmapCommands.begin(); it2 != s_hashmapCommands.end(); ++it2 )
    {
        wxString key = it2->first;
        wxPackageCommandSet &value = it2->second;

        for (size_t i=0; i<value.GetCount(); i++)
            wxLogDebug(wxT("Commandset [%s] - command '%s'"),
                    key.c_str(), value[i].GetCommand().c_str());
    }
}
#endif

// static
bool wxPresetXMLDescriptor::Load(const wxString &filename)
{
    wxFileInputStream stream(filename);
    if (!stream.Ok())
        return false;

    // the XML parsed by the following instance will be
    // saved in static vars and thus nothing will be lost
    wxPresetXMLDescriptor doc;
    return doc.Load(stream);
}

bool wxPresetXMLDescriptor::Load(wxInputStream &input)
{
    wxXmlDocument doc;

    if (!doc.Load(input))
        return false;

    if (!doc.GetRoot() ||
        doc.GetRoot()->GetName() != wxT("wxpackage-presets") ||
        doc.GetRoot()->GetPropVal(wxT("version"), wxT("1.0")) != wxT("1.0"))
        return false;

    wxXmlNode *child = doc.GetRoot()->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("option-set") &&
            child->HasProp(wxT("name")))
        {
            // load name & description of this preset
            wxString name = child->GetPropVal(wxT("name"), wxEmptyString);
            wxString desc = child->GetPropVal(wxT("description"), wxEmptyString);

            // load options it contains
            wxPackageCommandOptionArray preset(LoadOptions(child));
            preset.SetDescription(desc);

            if (preset.IsOk())
                s_hashmapOptions[name] = preset;
            else
                wxLogWarning(wxT("Discarding invalid option set named '%s'"), 
                             name.c_str());
        }
        else if (child->GetName() == wxT("command-set") &&
                child->HasProp(wxT("name")))
        {
            // load name & description of this preset
            wxString name = child->GetPropVal(wxT("name"), wxEmptyString);
            wxString desc = child->GetPropVal(wxT("description"), wxEmptyString);

            // load commands it contains
            wxPackageCommandSet cmdset(LoadCommandSet(child));
            cmdset.SetDescription(desc);

            if (cmdset.IsOk())
                s_hashmapCommands[name] = cmdset;
            else
                wxLogWarning(wxT("Discarding invalid command set named '%s'"), 
                             name.c_str());
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    return true;
}

wxPackageCommandSet wxPresetXMLDescriptor::LoadCommandSet(const wxXmlNode *n)
{
    wxPackageCommandSet ret;

    wxString buildsys = n->GetPropVal(wxT("buildsys"), wxEmptyString);
    wxString vars = n->GetPropVal(wxT("vars"), wxEmptyString);

    ret.SetBuildSystemType(wxString2PackageBuildSystemType(buildsys));
    ret.SetVars(vars);

    wxXmlNode *child = n->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("if"))
        {
            wxPackageCondition current = LoadCondition(child);

            // command sets are already buildsystem-specific thus
            // having a buildsys attribute in this <if> node does
            // not make sense!
            if (!child->HasProp(wxT("buildsys")))
            {
                wxXmlNode *cmd = child->GetChildren();
                while (cmd)
                {
                    if (cmd->GetName() == wxT("cmd"))
                    {
                        wxPackageCommand c(LoadCommand(cmd));
                        c.SetCondition(current);
                        if (c.IsOk())
                            ret.Add(c);
                        else
                            wxLogWarning(wxT("Discarding invalid <cmd> '%s'"),
                                         c.GetCommand().c_str());
                    }
                    else
                        HandleUnknownTag(cmd);

                    cmd = cmd->GetNext();
                }
            }
            else
                wxLogWarning(wxT("Discarding invalid <if> command"));
        }
        else if (child->GetName() == wxT("cmd"))
        {
            wxPackageCommand c(LoadCommand(child));
            if (c.IsOk())
                ret.Add(c);
            else
                wxLogWarning(wxT("Discarding invalid <cmd> '%s'"),
                             c.GetCommand().c_str());
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    return ret;
}

// static
bool wxPresetXMLDescriptor::SavePresets(const wxString &filename)
{
    // skip this save if all arrays are marked as "clean"
    // NB: this kind of check cannot be done in the other overload of
    //     SavePresets() as we need to do it *before* creating the
    //     wxFileOutputStream for given filename (which resets the contents
    //     of that file if it already exists).
    bool allclean = true;
    for( wxPackageCommandOptionArrayHashMap::iterator it = s_hashmapOptions.begin();
        it != s_hashmapOptions.end(); ++it )
    {
        if (s_hashmapOptions[it->first].IsDirty())
        {
            allclean = false;
            break;
        }
    }

    if (allclean)
        return true;

    wxFileOutputStream stream(filename);
    if (!stream.Ok())
        return false;

    // the XML parsed by the following instance will be
    // saved in static vars and thus nothing will be lost
    wxPresetXMLDescriptor doc;
    return doc.SavePresets(stream);
}

bool wxPresetXMLDescriptor::SavePresets(wxOutputStream &out)
{
    wxXmlNode *root = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("wxpackage-presets"));
    root->AddProperty(wxT("version"), wxT("1.0"));

    // place a std comment at the beginning
    root->AddChild(new wxXmlNode(wxXML_COMMENT_NODE, wxEmptyString,
                   wxString::Format(wxT(" Preset file for the wxWidgets Package Manager. Automatically generated in date %s. "),
                                    wxDateTime::Now().Format(wxT("%d %b %Y")).c_str())));
    root->AddChild(new wxXmlNode(wxXML_TEXT_NODE, wxEmptyString, wxT("\n\n")));

    // create all <option-set> nodes
    root->AddChild(new wxXmlNode(wxXML_COMMENT_NODE, wxEmptyString,
                                wxT(" The option presets: ")));
    for( wxPackageCommandOptionArrayHashMap::iterator it = s_hashmapOptions.begin();
        it != s_hashmapOptions.end(); ++it )
    {
        wxPackageCommandOptionArray &arr = it->second;

        // if this option set is empty skip it completely!
        if (arr.GetCount() == 0)
            continue;

        wxXmlNode *child = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("option-set"));
        child->AddProperty(wxT("name"), it->first);
        child->AddProperty(wxT("description"), arr.GetDescription());

        // add <if> nodes with <option> children
        wxIfNodeListManager list(this, child);
        list.SaveOptions(arr);

        // link with parent
        root->AddChild(child);
    }

    // create all <command-set> nodes
    root->AddChild(new wxXmlNode(wxXML_TEXT_NODE, wxEmptyString, wxT("\n\n")));
    root->AddChild(new wxXmlNode(wxXML_COMMENT_NODE, wxEmptyString,
                                wxT(" The command presets: ")));
    for( wxPackageCommandSetHashMap::iterator it = s_hashmapCommands.begin();
        it != s_hashmapCommands.end(); ++it )
    {
        wxPackageCommandSet &arr = it->second;

        // if this option set is empty skip it completely!
        if (arr.GetCount() == 0)
            continue;

        wxXmlNode *child = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("command-set"));
        child->AddProperty(wxT("name"), it->first);
        child->AddProperty(wxT("description"), arr.GetDescription());

        // create property strings
        child->AddProperty(wxT("vars"),
			wxArray2String(arr.GetVars()));
        child->AddProperty(wxT("buildsys"),
            wxPackageBuildSystemType2String(arr.GetBuildSystemType()));

        // add <if> nodes with <cmd> children
        wxIfNodeListManager list(this, child);
        list.SaveCommands(arr);

        // link with parent
        root->AddChild(child);
    }

    // finally, save the document
    wxXmlDocument doc;
    doc.SetRoot(root);
    return doc.Save(out, 2 /* default indentation level */);
}



// ----------------------------------------------------------------------------
// wxPackageRepositoryXMLDescriptor
// ----------------------------------------------------------------------------

bool wxPackageRepositoryXMLDescriptor::Load(const wxString &filename)
{
    wxFFileInputStream input(filename);
    if (!input.IsOk())
        return false;

    // WXL files are gzipped:
    wxZlibInputStream unzipped(input);
    return wxPackageXML::Load(unzipped);
}

bool wxPackageRepositoryXMLDescriptor::AddPackage(const wxString &pkgfile)
{
    wxPackage pkg;
    if (!pkg.LoadCompressedPackage(pkgfile))
    {
        wxLogError(wxT("Cannot load the '%s' package."), pkgfile.c_str());
        return false;
    }

    // the UpdateMD5() and UpdateCompressedSize() functions
    // have been already called on 'pkg' by LoadCompressedPackage

    if (!GetRoot())
    {
        // create our root first
        SetRoot(new wxXmlNode(wxXML_ELEMENT_NODE, wxT("wxpackagelist")));
        GetRoot()->AddProperty(wxT("version"), wxT("1.0"));
    }

    // SetPackageInfo will create all the XML tree for the given package.
    // NB: the XML document created won't have any comment in it but this
    //     is good for us as WXL are not going to be read by humans and
    //     leaving out comments makes them smaller and faster to download !
    wxPackageXMLDescriptor wxp;
    wxp.SetPackageInfo(pkg);

    // don't copy the entire package XML tree; detach it from
    // the wxPackageXMLDescriptor and attach it to our tree
    wxXmlNode *root = wxp.DetachRoot();
    GetRoot()->AddChild(root);

    root->AddProperty(wxT("size"), pkg.GetCompressedSize().ToString());
    root->AddProperty(wxT("md5"), pkg.GetMD5());
    root->AddProperty(wxT("file"), wxFileName(pkgfile).GetFullName());

    return true;
}

wxPackageArray wxPackageRepositoryXMLDescriptor::GetPackageArray(const wxString &repourl)
{
    wxPackageArray ret;

    // parse this package list
    if (!GetRoot() ||
        GetRoot()->GetName() != wxT("wxpackagelist") ||
        GetRoot()->GetPropVal(wxT("version"), wxT("1.0")) != wxT("1.0"))
        return wxEmptyPackageArray;

    // if the repository is at e.g. http://wxwpm.sourceforge.net/packages/packages.wxl
    // we want to use as download link for package "abc.wxz" the URL:
    // http://wxwpm.sourceforge.net/packages/abc.wxz
    wxString baseurl = repourl.BeforeLast(wxT('/'));

    wxXmlNode *child = GetRoot()->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("wxpackage"))
        {
            wxPackageXMLDescriptor wxp;

            // don't copy the 'child' node owned by *this
            wxp.SetRoot(child);

            // do parse the XML
            if (wxp.Parse())
            {
                // get package info data and complete it with some
                // properties only present in the <wxpackage> tags which
                // are children of the <wxpackagelist> tag
                wxPackage pkg(wxp.GetPackageInfo());

                wxString url, filename = child->GetPropVal(wxT("file"), wxEmptyString);
                if (!baseurl.IsEmpty() && !filename.IsEmpty())
                     url = baseurl + wxT("/") + filename;

                pkg.SetRepoDownloadLink(url);
                pkg.SetMD5(child->GetPropVal(wxT("md5"), wxEmptyString));

                if (child->HasProp(wxT("size")))
                {
                    unsigned long sz;
                    if (child->GetPropVal(wxT("size"), wxEmptyString).ToULong(&sz))
                        pkg.SetCompressedSize(sz);
                }

                if (pkg.IsOk())
                {
                    pkg.SetRemoteStatus();
                    ret.Add(pkg);
                }
                else
                    wxLogWarning(wxT("Discarding invalid package named '%s'"),
                                 pkg.GetName().c_str());
            }

            // before the wxPackageXMLDescriptor gets destroyed,
            // remove our child from the root of that instance
            wxp.DetachRoot();
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    if (!ret.IsOk())
        return wxEmptyPackageArray;
    return ret;
}

