/////////////////////////////////////////////////////////////////////////////
// Name:        wxrc.cpp
// Purpose:     XML resource compiler
// Author:      Vaclav Slavik, Eduardo Marques <edrdo@netcabo.pt>
// Created:     2000/03/05
// Copyright:   (c) 2000 Vaclav Slavik
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

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


// for all others, include the necessary headers
#ifndef WX_PRECOMP
    #include "wx/app.h"
    #include "wx/log.h"
    #include "wx/wxcrtvararg.h"
#endif

#include "wx/cmdline.h"
#include "wx/xml/xml.h"
#include "wx/ffile.h"
#include "wx/filename.h"
#include "wx/wfstream.h"
#include "wx/utils.h"
#include "wx/mimetype.h"
#include "wx/vector.h"

#include <memory>

class XRCWidgetData
{
public:
    XRCWidgetData(const wxString& vname,const wxString& vclass)
        : m_class(vclass), m_name(vname)  {}
    const wxString& GetName() const { return m_name; }
    const wxString& GetClass() const { return m_class; }
private:
    wxString m_class;
    wxString m_name;
};

#include "wx/arrimpl.cpp"
WX_DECLARE_OBJARRAY(XRCWidgetData,ArrayOfXRCWidgetData);
WX_DEFINE_OBJARRAY(ArrayOfXRCWidgetData)

class XRCWndClassData
{
private:
    wxString m_className;
    wxString m_parentClassName;
    ArrayOfXRCWidgetData m_wdata;

    void BrowseXmlNode(wxXmlNode* node)
    {
        wxString classValue;
        wxString nameValue;
        wxXmlNode* children;
        while (node)
        {
            if (node->GetName() == wxT("object")
                && node->GetAttribute(wxT("class"),&classValue)
                && node->GetAttribute(wxT("name"),&nameValue))
            {
                m_wdata.Add(XRCWidgetData(nameValue,classValue));
            }
            children = node->GetChildren();
            if (children)
                    BrowseXmlNode(children);
            node = node->GetNext();
        }
    }

public:
    XRCWndClassData(const wxString& className,
                    const wxString& parentClassName,
                    const wxXmlNode* node) :
        m_className(className) , m_parentClassName(parentClassName)
    {
        BrowseXmlNode(node->GetChildren());
    }

    const ArrayOfXRCWidgetData& GetWidgetData()
    {
        return m_wdata;
    }

    bool CanBeUsedWithXRCCTRL(const wxString& name)
    {
        if (name == wxT("tool") ||
            name == wxT("data") ||
            name == wxT("unknown") ||
            name == wxT("notebookpage") ||
            name == wxT("separator") ||
            name == wxT("sizeritem") ||
            name == wxT("wxMenu") ||
            name == wxT("wxMenuBar") ||
            name == wxT("wxMenuItem") ||
            name.EndsWith(wxT("Sizer")) )
        {
            return false;
        }
        return true;
    }

    void GenerateHeaderCode(wxFFile& file)
    {

        file.Write(wxT("class ") + m_className + wxT(" : public ") + m_parentClassName
                   + wxT(" {\nprotected:\n"));
        size_t i;
        for(i=0;i<m_wdata.GetCount();++i)
        {
            const XRCWidgetData& w = m_wdata.Item(i);
            if( !CanBeUsedWithXRCCTRL(w.GetClass()) ) continue;
            if( w.GetName().empty() ) continue;
            file.Write(
                wxT(" ") + w.GetClass() + wxT("* ") + w.GetName()
                + wxT(";\n"));
        }
        file.Write(wxT("\nprivate:\n void InitWidgetsFromXRC(wxWindow *parent){\n")
                   wxT("  wxXmlResource::Get()->LoadObject(this,parent,wxT(\"")
                   +  m_className
                   +  wxT("\"), wxT(\"")
                   +  m_parentClassName
                   +  wxT("\"));\n"));
        for(i=0;i<m_wdata.GetCount();++i)
        {
            const XRCWidgetData& w = m_wdata.Item(i);
            if( !CanBeUsedWithXRCCTRL(w.GetClass()) ) continue;
            if( w.GetName().empty() ) continue;
            file.Write( wxT("  ")
                        + w.GetName()
                        + wxT(" = XRCCTRL(*this,\"")
                        + w.GetName()
                        + wxT("\",")
                        + w.GetClass()
                        + wxT(");\n"));
        }
        file.Write(wxT(" }\n"));

        file.Write( wxT("public:\n"));

        file.Write
             (
                m_className +
                wxT("(wxWindow *parent=nullptr){\n") +
                wxT("  InitWidgetsFromXRC((wxWindow *)parent);\n")
                wxT(" }\n")
                wxT("};\n")
             );
    }
};
WX_DECLARE_OBJARRAY(XRCWndClassData,ArrayOfXRCWndClassData);
WX_DEFINE_OBJARRAY(ArrayOfXRCWndClassData)

struct ExtractedString
{
    ExtractedString() : lineNo(-1) {}
    ExtractedString(const wxString& str_,
                    const wxString& filename_, int lineNo_)
        : str(str_), filename(filename_), lineNo(lineNo_)
    {}

    wxString str;

    wxString filename;
    int      lineNo;
};

typedef wxVector<ExtractedString> ExtractedStrings;


class XmlResApp : public wxAppConsole
{
public:
    // don't use builtin cmd line parsing:
    virtual bool OnInit() override { return true; }
    virtual int OnRun() override;

private:
    void ParseParams(const wxCmdLineParser& cmdline);
    void CompileRes();
    wxArrayString PrepareTempFiles();
    void FindFilesInXML(wxXmlNode *node, wxArrayString& flist, const wxString& inputPath);

    wxString GetInternalFileName(const wxString& name, const wxArrayString& flist);
    void DeleteTempFiles(const wxArrayString& flist);
    void MakePackageZIP(const wxArrayString& flist);
    void MakePackageCPP(const wxArrayString& flist);
    void MakePackagePython(const wxArrayString& flist);

    void OutputGettext();
    ExtractedStrings FindStrings();
    ExtractedStrings FindStrings(const wxString& filename, wxXmlNode *node);

    bool Validate();

    bool flagVerbose, flagCPP, flagPython, flagGettext, flagValidate, flagValidateOnly;
    wxString parOutput, parFuncname, parOutputPath, parSchemaFile;
    wxArrayString parFiles;
    int retCode;

    ArrayOfXRCWndClassData aXRCWndClassData;
        bool flagH;
        void GenCPPHeader();
};

wxIMPLEMENT_APP_CONSOLE(XmlResApp);

int XmlResApp::OnRun()
{
    wxGCC_WARNING_SUPPRESS(missing-field-initializers)

    static const wxCmdLineEntryDesc cmdLineDesc[] =
    {
        { wxCMD_LINE_SWITCH, "h", "help",  "show help message", wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
        { wxCMD_LINE_SWITCH, "v", "verbose", "be verbose" },
        { wxCMD_LINE_SWITCH, "e", "extra-cpp-code",  "output C++ header file with XRC derived classes" },
        { wxCMD_LINE_SWITCH, "c", "cpp-code",  "output C++ source rather than .rsc file" },
        { wxCMD_LINE_SWITCH, "p", "python-code",  "output wxPython source rather than .rsc file" },
        { wxCMD_LINE_SWITCH, "g", "gettext",  "output list of translatable strings (to stdout or file if -o used)" },
        { wxCMD_LINE_OPTION, "n", "function",  "C++/Python function name (with -c or -p) [InitXmlResource]" },
        { wxCMD_LINE_OPTION, "o", "output",  "output file [resource.xrs/cpp]" },
        { wxCMD_LINE_SWITCH, "",  "validate", "check XRC correctness (in addition to other processing)" },
        { wxCMD_LINE_SWITCH, "",  "validate-only", "check XRC correctness and do nothing else" },
        { wxCMD_LINE_OPTION, "",  "xrc-schema", "RELAX NG schema file to validate against (optional)" },
#if 0 // not yet implemented
        { wxCMD_LINE_OPTION, "l", "list-of-handlers",  "output list of necessary handlers to this file" },
#endif
        { wxCMD_LINE_PARAM,  nullptr, nullptr, "input file(s)",
              wxCMD_LINE_VAL_STRING,
              wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_OPTION_MANDATORY },

        wxCMD_LINE_DESC_END
    };

    wxGCC_WARNING_RESTORE(missing-field-initializers)

    wxCmdLineParser parser(cmdLineDesc, argc, argv);

    switch (parser.Parse())
    {
        case -1:
            return 0;

        case 0:
            retCode = 0;
            ParseParams(parser);

            if (flagValidate)
            {
                if ( !Validate() )
                    return 2;
                if ( flagValidateOnly )
                    return 0;
            }

            if (flagGettext)
                OutputGettext();
            else
                CompileRes();
            return retCode;
    }
    return 1;
}




void XmlResApp::ParseParams(const wxCmdLineParser& cmdline)
{
    flagGettext = cmdline.Found("g");
    flagVerbose = cmdline.Found("v");
    flagCPP = cmdline.Found("c");
    flagPython = cmdline.Found("p");
    flagH = flagCPP && cmdline.Found("e");
    flagValidateOnly = cmdline.Found("validate-only");
    flagValidate = flagValidateOnly || cmdline.Found("validate");

    cmdline.Found("xrc-schema", &parSchemaFile);

    if (!cmdline.Found("o", &parOutput))
    {
        if (flagGettext)
            parOutput = wxEmptyString;
        else
        {
            if (flagCPP)
                parOutput = wxT("resource.cpp");
            else if (flagPython)
                parOutput = wxT("resource.py");
            else
                parOutput = wxT("resource.xrs");
        }
    }
    if (!parOutput.empty())
    {
        parOutput = wxFileName(parOutput).GetAbsolutePath();
        parOutputPath = wxPathOnly(parOutput);
    }
    if (parOutputPath.empty()) parOutputPath = wxT(".");

    if (!cmdline.Found("n", &parFuncname))
        parFuncname = wxT("InitXmlResource");

    for (size_t i = 0; i < cmdline.GetParamCount(); i++)
    {
#ifdef __WINDOWS__
        wxString fn=wxFindFirstFile(cmdline.GetParam(i), wxFILE);
        while (!fn.empty())
        {
            parFiles.Add(fn);
            fn=wxFindNextFile();
        }
#else
        parFiles.Add(cmdline.GetParam(i));
#endif
    }
}




void XmlResApp::CompileRes()
{
    wxArrayString files = PrepareTempFiles();

    if ( wxFileExists(parOutput) )
        wxRemoveFile(parOutput);

    if (!retCode)
    {
        if (flagCPP){
            MakePackageCPP(files);
            if (flagH)
                GenCPPHeader();
        }
        else if (flagPython)
            MakePackagePython(files);
        else
            MakePackageZIP(files);
    }

    DeleteTempFiles(files);
}


wxString XmlResApp::GetInternalFileName(const wxString& name, const wxArrayString& flist)
{
    wxString name2 = name;
    name2.Replace(wxT(":"), wxT("_"));
    name2.Replace(wxT("/"), wxT("_"));
    name2.Replace(wxT("\\"), wxT("_"));
    name2.Replace(wxT("*"), wxT("_"));
    name2.Replace(wxT("?"), wxT("_"));

    const wxString parOutput_fileName{wxFileNameFromPath(parOutput)};
    wxString s{wxString::Format(wxT("%s$%s"), parOutput_fileName, name2)};

    if (wxFileExists(s) && flist.Index(s) == wxNOT_FOUND)
    {
        for (int i = 0;; i++)
        {
            s.Printf(wxT("%s$%03i-%s"), parOutput_fileName, i, name2);
            if (!wxFileExists(s) || flist.Index(s) != wxNOT_FOUND)
                break;
        }
    }
    return s;
}

wxArrayString XmlResApp::PrepareTempFiles()
{
    wxArrayString flist;

    for (size_t i = 0; i < parFiles.GetCount(); i++)
    {
        if (flagVerbose)
            wxPrintf(wxT("processing %s...\n"), parFiles[i]);

        wxXmlDocument doc;

        if (!doc.Load(parFiles[i]))
        {
            wxLogError(wxT("Error parsing file ") + parFiles[i]);
            retCode = 1;
            continue;
        }

        wxString name, ext, path;
        wxFileName::SplitPath(parFiles[i], &path, &name, &ext);

        FindFilesInXML(doc.GetRoot(), flist, path);
        if (flagH)
        {
            wxXmlNode* node = (doc.GetRoot())->GetChildren();
                wxString classValue,nameValue;
                while(node){
                    if(node->GetName() == wxT("object")
                     && node->GetAttribute(wxT("class"),&classValue)
                     && node->GetAttribute(wxT("name"),&nameValue)){

                      aXRCWndClassData.Add(
                        XRCWndClassData(nameValue,classValue,node)
                      );
                    }
                    node = node -> GetNext();
            }
        }
        wxString internalName = GetInternalFileName(parFiles[i], flist);

        doc.Save(parOutputPath + wxFILE_SEP_PATH + internalName);
        flist.Add(internalName);
    }

    return flist;
}


// Does 'node' contain filename information at all?
static bool NodeContainsFilename(wxXmlNode *node)
{
   const wxString name = node->GetName();

   // Any bitmaps (bitmap2 is used for disabled toolbar buttons):
   if ( name == wxT("bitmap") || name == wxT("bitmap2") )
       return true;

   if ( name == wxT("icon") )
       return true;

   // wxBitmapButton:
   wxXmlNode *parent = node->GetParent();
   if (parent != nullptr &&
       parent->GetAttribute(wxT("class"), wxT("")) == wxT("wxBitmapButton") &&
       (name == wxT("focus") ||
        name == wxT("disabled") ||
        name == wxT("hover") ||
        name == wxT("selected")))
       return true;

   // wxBitmap or wxIcon toplevel resources:
   if ( name == wxT("object") )
   {
       wxString klass = node->GetAttribute(wxT("class"), wxEmptyString);
       if (klass == wxT("wxBitmap") ||
               klass == wxT("wxIcon") ||
                klass == wxT("data") )
           return true;
   }

   // wxAnimationCtrl animations:
   if ( name == wxS("animation") )
       return true;

   // URLs in wxHtmlWindow:
   if ( name == wxT("url") &&
        parent != nullptr &&
        parent->GetAttribute(wxT("class"), wxT("")) == wxT("wxHtmlWindow") )
   {
       // FIXME: this is wrong for e.g. http:// URLs
       return true;
   }

   return false;
}

// find all files mentioned in structure, e.g. <bitmap>filename</bitmap>
void XmlResApp::FindFilesInXML(wxXmlNode *node, wxArrayString& flist, const wxString& inputPath)
{
    // Is 'node' XML node element?
    if (node == nullptr) return;
    if (node->GetType() != wxXML_ELEMENT_NODE) return;

    bool containsFilename = NodeContainsFilename(node);

    wxXmlNode *n = node->GetChildren();
    while (n)
    {
        if (containsFilename &&
            (n->GetType() == wxXML_TEXT_NODE ||
             n->GetType() == wxXML_CDATA_SECTION_NODE))
        {
            // At least <bitmap> content can include several semi-colon
            // separated paths corresponding to the different resolutions of
            // the bitmap, so check for this.
            wxArrayString internalNames;
            const wxArrayString paths = wxSplit(n->GetContent(), ';', '\0');
            for (size_t i = 0; i < paths.size(); ++i)
            {
                const wxString& path = paths[i];

                wxString fullname;
                if (wxIsAbsolutePath(path) || inputPath.empty())
                    fullname = path;
                else
                    fullname = inputPath + wxFILE_SEP_PATH + path;

                if (flagVerbose)
                    wxPrintf(wxT("adding     %s...\n"), fullname);

                wxFileInputStream sin(fullname);
                if (!sin)
                {
                    // Note that the full name was already given in the error
                    // message logged by wxFileInputStream itself, so don't repeat
                    // it here.
                    wxLogError("Failed to read file referenced by \"%s\" at %d",
                               node->GetName(), node->GetLineNumber());
                    retCode = 1;
                }
                else
                {
                    wxString filename = GetInternalFileName(path, flist);

                    // Copy the entire stream to the output file.
                    wxFileOutputStream sout(parOutputPath + wxFILE_SEP_PATH + filename);
                    if ( sin.Read(sout).GetLastError() != wxSTREAM_EOF || !sout )
                    {
                        wxLogError("Failed to save \"%s\" referenced by \"%s\" at %d"
                                   " to a temporary file",
                                   fullname, node->GetName(), node->GetLineNumber());
                        retCode = 1;
                    }
                    else
                    {
                        internalNames.push_back(filename);

                        if (flist.Index(filename) == wxNOT_FOUND)
                            flist.Add(filename);
                    }
                }
            }

            n->SetContent(wxJoin(internalNames, ';', '\0'));
        }

        // subnodes:
        if (n->GetType() == wxXML_ELEMENT_NODE)
            FindFilesInXML(n, flist, inputPath);

        n = n->GetNext();
    }
}



void XmlResApp::DeleteTempFiles(const wxArrayString& flist)
{
    for (size_t i = 0; i < flist.GetCount(); i++)
        wxRemoveFile(parOutputPath + wxFILE_SEP_PATH + flist[i]);
}



void XmlResApp::MakePackageZIP(const wxArrayString& flist)
{
    wxString files;

    for (size_t i = 0; i < flist.GetCount(); i++)
        files += flist[i] + wxT(" ");
    files.RemoveLast();

    if (flagVerbose)
        wxPrintf(wxT("compressing %s...\n"), parOutput);

    wxString cwd = wxGetCwd();
    wxSetWorkingDirectory(parOutputPath);
    int execres = wxExecute(wxT("zip -9 -j ") +
                            wxString(flagVerbose ? wxT("\"") : wxT("-q \"")) +
                            parOutput + wxT("\" ") + files,
                            wxEXEC_BLOCK);
    wxSetWorkingDirectory(cwd);
    if (execres == -1)
    {
        wxLogError(wxT("Unable to execute zip program. Make sure it is in the path."));
        wxLogError(wxT("You can download it at http://www.cdrom.com/pub/infozip/"));
        retCode = 1;
        return;
    }
}


// This function returns empty string on any file IO error.
static wxString FileToCppArray(wxString filename, int num)
{
    wxString output;
    wxString tmp;
    wxString snum;
    wxFFile file(filename, wxT("rb"));
    wxFileOffset offset = file.Length();
    if ( offset < 0 )
    {
        wxLogError(R"(Error getting size of the file "%s")", filename);
        return {};
    }

    // 32-bit Unix systems and 32-bit and 64-bit Windows systems do not support
    // loader modules larger than 4 GiB. For 64-bit Windows systems, even if
    // the virtual address space of a process exceeds the 32-bit range, each
    // individual loader module cannot by itself exceed the 32-bit range
    // (https://duckduckgo.com/?q=pecoff_v8). So even if our resource compiler
    // supported 64-bit-sized files, it might not be able to process the
    // resulting C++ code on some platforms/toolchains. Therefore, we limit the
    // size of an individual resource file to 32-bit range:
    if (offset != static_cast<wxUint32>(offset))
    {
        wxLogError(R"(Resource file "%s" is too big (%llu bytes) and can't be processed)",
                   offset);
        return {};
    }

    // This cast must succeed now, after the check above.
    const size_t lng = wx_truncate_cast(size_t, offset);

    snum.Printf(wxT("%i"), num);
    output.Printf(wxT("static size_t xml_res_size_") + snum + wxT(" = %lu;\n"),
                  static_cast<unsigned long>(lng));
    output += wxT("static unsigned char xml_res_file_") + snum + wxT("[] = {\n");
    // we cannot use string literals because MSVC is dumb wannabe compiler
    // with arbitrary limitation to 2048 strings :(

    std::vector<unsigned char> buffer(lng);
    file.Read(&buffer[0], lng);

    for (size_t i = 0, linelng = 0; i < lng; i++)
    {
        tmp.Printf(wxT("%i"), buffer[i]);
        if (i != 0) output << wxT(',');
        if (linelng > 70)
        {
            linelng = 0;
            output << wxT("\n");
        }
        output << tmp;
        linelng += tmp.length()+1;
    }

    output += wxT("};\n\n");

    return output;
}


void XmlResApp::MakePackageCPP(const wxArrayString& flist)
{
    wxFFile file(parOutput, wxT("wt"));
    unsigned i;

    if (flagVerbose)
        wxPrintf(wxT("creating C++ source file %s...\n"), parOutput);

    file.Write(""
"//\n"
"// This file was automatically generated by wxrc, do not edit by hand.\n"
"//\n\n"
"#include <wx/wxprec.h>\n"
"\n"
""
"#include <wx/filesys.h>\n"
"#include <wx/fs_mem.h>\n"
"#include <wx/xrc/xmlres.h>\n"
"#include <wx/xrc/xh_all.h>\n"
"\n"

/*
  Define a helper function to ensure that conversions from `const wxChar *` to
  `const wxString &` are centralized in a single place, as this results in
  much faster compilation time and much smaller generated machine code.

  For example, for 10,000 binary resources replacing the previously used macro
  with this function results in the following changes:

  Compiler (with "-g -O2") | Time (seconds) | RAM (KiB) | Machine code (bytes)
  -------------------------| -------------- | --------- | ---------------------
  g++-11 before            |            158 | 3,316,384 |             1,821,235
  g++-11 now               |             16 |   718,080 |               271,987
  clang++-14 before        |            158 | 5,263,864 |             2,820,830
  clang++-14 now           |              5 |   544,144 |               271,080
*/
"static void XRC_ADD_FILE(const wxChar *filename, const void *binarydata, size_t size, const wxChar *mimetype)\n"
"{\n"
"    #if wxCHECK_VERSION(2,8,5) && wxABI_VERSION >= 20805\n"
"        return wxMemoryFSHandler::AddFileWithMimeType(filename, binarydata, size, mimetype);\n"
"    #else\n"
"        return wxMemoryFSHandler::AddFile(filename, binarydata, size);\n"
"    #endif\n"
"}\n"
"\n");

    for (i = 0; i < flist.GetCount(); i++)
    {
        const wxString result =
            FileToCppArray(parOutputPath + wxFILE_SEP_PATH + flist[i], i);
        if (result.empty())
        {
            retCode = 2;
            return;
        }
        file.Write(result);
    }

    file.Write(""
"void " + parFuncname + "()\n"
"{\n"
"\n"
"    // Check for memory FS. If not present, load the handler:\n"
"    {\n"
"        wxMemoryFSHandler::AddFile(wxT(\"XRC_resource/dummy_file\"), wxT(\"dummy one\"));\n"
"        wxFileSystem fsys;\n"
"        wxFSFile *f = fsys.OpenFile(wxT(\"memory:XRC_resource/dummy_file\"));\n"
"        wxMemoryFSHandler::RemoveFile(wxT(\"XRC_resource/dummy_file\"));\n"
"        if (f) delete f;\n"
"        else wxFileSystem::AddHandler(new wxMemoryFSHandler);\n"
"    }\n"
"\n");

    for (i = 0; i < flist.GetCount(); i++)
    {
        wxString s;

        wxString mime;
        wxString ext = wxFileName(flist[i]).GetExt();
        if ( ext.Lower() == wxT("xrc") )
            mime = wxT("text/xml");
#if wxUSE_MIMETYPE
        else
        {
            std::unique_ptr<wxFileType>
                ft{wxTheMimeTypesManager->GetFileTypeFromExtension(ext)};
            if ( ft )
                ft->GetMimeType(&mime);
        }
#endif // wxUSE_MIMETYPE

        s.Printf("    XRC_ADD_FILE(wxT(\"XRC_resource/" + flist[i] +
                 "\"), xml_res_file_%u, xml_res_size_%u, wxT(\"%s\"));\n",
                 i, i, mime.c_str());
        file.Write(s);
    }

    for (i = 0; i < parFiles.GetCount(); i++)
    {
        file.Write("    wxXmlResource::Get()->Load(wxT(\"memory:XRC_resource/" +
                   GetInternalFileName(parFiles[i], flist) + "\"));\n");
    }

    file.Write("}\n");


}

void XmlResApp::GenCPPHeader()
{
    // Generate the output header in the same directory as the source file.
    wxFileName headerName(parOutput);
    headerName.SetExt("h");

    wxFFile file(headerName.GetFullPath(), wxT("wt"));
    file.Write(
"//\n"
"// This file was automatically generated by wxrc, do not edit by hand.\n"
"//\n\n"
"#ifndef __"  + headerName.GetName() + "_h__\n"
"#define __"  + headerName.GetName() + "_h__\n"
);
    for(size_t i=0;i<aXRCWndClassData.GetCount();++i){
                aXRCWndClassData.Item(i).GenerateHeaderCode(file);
    }
    file.Write(
                "\nvoid \n"
                + parFuncname
                + "();\n#endif\n");
}

static wxString FileToPythonArray(wxString filename, int num)
{
    wxString output;
    wxString tmp;
    wxString snum;
    wxFFile file(filename, wxT("rb"));
    wxFileOffset offset = file.Length();
    wxASSERT_MSG( offset >= 0 , wxT("Invalid file length") );

    const size_t lng = wx_truncate_cast(size_t, offset);
    wxASSERT_MSG( static_cast<wxFileOffset>(lng) == offset,
                  wxT("Huge file not supported") );

    snum.Printf(wxT("%i"), num);
    output = "    xml_res_file_" + snum + " = '''\\\n";

    std::vector<unsigned char> buffer(lng);
    file.Read(&buffer[0], lng);

    for (size_t i = 0, linelng = 0; i < lng; i++)
    {
        unsigned char c = buffer[i];
        if (c == '\n')
        {
            tmp = (wxChar)c;
            linelng = 0;
        }
        else if (c < 32 || c > 127 || c == '\'')
            tmp.Printf(wxT("\\x%02x"), c);
        else if (c == '\\')
            tmp = wxT("\\\\");
        else
            tmp = (wxChar)c;
        if (linelng > 70)
        {
            linelng = 0;
            output << wxT("\\\n");
        }
        output << tmp;
        linelng += tmp.length();
    }

    output += wxT("'''\n\n");

    return output;
}


void XmlResApp::MakePackagePython(const wxArrayString& flist)
{
    wxFFile file(parOutput, wxT("wt"));
    unsigned i;

    if (flagVerbose)
        wxPrintf(wxT("creating Python source file %s...\n"), parOutput);

    file.Write(
       "#\n"
       "# This file was automatically generated by wxrc, do not edit by hand.\n"
       "#\n\n"
       "import wx\n"
       "import wx.xrc\n\n"
    );


    file.Write("def " + parFuncname + "():\n");

    for (i = 0; i < flist.GetCount(); i++)
        file.Write(
          FileToPythonArray(parOutputPath + wxFILE_SEP_PATH + flist[i], i));

    file.Write(
        "    # check if the memory filesystem handler has been loaded yet, and load it if not\n"
        "    wx.MemoryFSHandler.AddFile('XRC_resource/dummy_file', 'dummy value')\n"
        "    fsys = wx.FileSystem()\n"
        "    f = fsys.OpenFile('memory:XRC_resource/dummy_file')\n"
        "    wx.MemoryFSHandler.RemoveFile('XRC_resource/dummy_file')\n"
        "    if f is not None:\n"
        "        f.Destroy()\n"
        "    else:\n"
        "        wx.FileSystem.AddHandler(wx.MemoryFSHandler())\n"
        "\n"
        "    # load all the strings as memory files and load into XmlRes\n"
        );


    for (i = 0; i < flist.GetCount(); i++)
    {
        wxString s;
        s.Printf("    wx.MemoryFSHandler.AddFile('XRC_resource/" + flist[i] +
                 "', xml_res_file_%u)\n", i);
        file.Write(s);
    }
    for (i = 0; i < parFiles.GetCount(); i++)
    {
        file.Write("    wx.xrc.XmlResource.Get().Load('memory:XRC_resource/" +
                   GetInternalFileName(parFiles[i], flist) + "')\n");
    }

    file.Write("\n");
}



void XmlResApp::OutputGettext()
{
    ExtractedStrings str = FindStrings();

    wxFFile fout;
    if (parOutput.empty())
        fout.Attach(stdout);
    else
        fout.Open(parOutput, wxT("wt"));

    for (ExtractedStrings::const_iterator i = str.begin(); i != str.end(); ++i)
    {
        const wxFileName filename(i->filename);

        wxString s;
        s.Printf("#line %d \"%s\"\n",
                 i->lineNo, filename.GetFullPath(wxPATH_UNIX));

        fout.Write(s);
        fout.Write("_(\"" + i->str + "\");\n");
    }

    if (parOutput.empty()) fout.Detach();
}



ExtractedStrings XmlResApp::FindStrings()
{
    ExtractedStrings arr, a2;

    for (size_t i = 0; i < parFiles.GetCount(); i++)
    {
        if (flagVerbose)
            wxPrintf(wxT("processing %s...\n"), parFiles[i]);

        wxXmlDocument doc;
        if (!doc.Load(parFiles[i]))
        {
            wxLogError(wxT("Error parsing file ") + parFiles[i]);
            retCode = 1;
            continue;
        }
        a2 = FindStrings(parFiles[i], doc.GetRoot());

        WX_APPEND_ARRAY(arr, a2);
    }

    return arr;
}



static wxString ConvertText(const wxString& str)
{
    wxString str2;
    const wxChar *dt;

    for (dt = str.c_str(); *dt; dt++)
    {
        if (*dt == wxT('_'))
        {
            if ( *(dt+1) == 0 )
                str2 << wxT('_');
            else if ( *(++dt) == wxT('_') )
                str2 << wxT('_');
            else
                str2 << wxT('&') << *dt;
        }
        else
        {
            switch (*dt)
            {
                case wxT('\n') : str2 << wxT("\\n"); break;
                case wxT('\t') : str2 << wxT("\\t"); break;
                case wxT('\r') : str2 << wxT("\\r"); break;
                case wxT('\\') : if ((*(dt+1) != 'n') &&
                                     (*(dt+1) != 't') &&
                                     (*(dt+1) != 'r'))
                                     str2 << wxT("\\\\");
                                 else
                                     str2 << wxT("\\");
                                 break;
                case wxT('"')  : str2 << wxT("\\\""); break;
                default        : str2 << *dt; break;
            }
        }
    }

    return str2;
}


enum ContentsKind
{
    Contents_NotTrans,  // Not a translatable text at all.
    Contents_TransOnly, // Translatable but not escaped text.
    Contents_Text       // Text, i.e. both translatable and escaped.
};

// Check if the given node contains translatable text and, if it does, whether
// it's escaped (i.e. parsed using GetText()) or not.
ContentsKind
GetNodeContentsKind(wxXmlNode& node, const wxString& contents)
{
    if ( node.GetName() == wxT("label") ||
         (node.GetName() == wxT("value") && !contents.IsNumber()) ||
         node.GetName() == wxT("help") ||
         node.GetName() == wxT("hint") ||
         node.GetName() == wxT("longhelp") ||
         node.GetName() == wxT("tooltip") ||
         node.GetName() == wxT("htmlcode") ||
         node.GetName() == wxT("title") ||
         node.GetName() == wxT("message") ||
         node.GetName() == wxT("note") ||
         node.GetName() == wxT("defaultdirectory") ||
         node.GetName() == wxT("defaultfilename") ||
         node.GetName() == wxT("defaultfolder") ||
         node.GetName() == wxT("filter") ||
         node.GetName() == wxT("caption") )
    {
        return Contents_Text;
    }

    // This one is special: it is translated in XRC, but its contents is not
    // escaped, except for the special case of wxRadioBox when it can be, if
    // "label" attribute is supplied.
    if ( node.GetName() == wxT("item") )
    {
        return node.GetAttribute(wxT("label"), wxT("0")) == wxT("1")
                ? Contents_Text
                : Contents_TransOnly;
    }

    return Contents_NotTrans;
}


ExtractedStrings
XmlResApp::FindStrings(const wxString& filename, wxXmlNode *node)
{
    ExtractedStrings arr;

    wxXmlNode *n = node;
    if (n == nullptr) return arr;
    n = n->GetChildren();

    while (n)
    {
        if ((node->GetType() == wxXML_ELEMENT_NODE) &&
            // parent is an element, i.e. has subnodes...
            (n->GetType() == wxXML_TEXT_NODE ||
            n->GetType() == wxXML_CDATA_SECTION_NODE))
            // ...it is textnode...
        {
            wxString s = n->GetContent();
            switch ( GetNodeContentsKind(*node, s) )
            {
                case Contents_NotTrans:
                    break;

                case Contents_Text:
                    s = ConvertText(s);
                    wxFALLTHROUGH;

                case Contents_TransOnly:
                    if (!flagGettext ||
                        node->GetAttribute(wxT("translate"), wxT("1")) != wxT("0"))
                    {
                        arr.push_back(ExtractedString(s, filename, n->GetLineNumber()));
                    }
                    break;
            }
        }

        // subnodes:
        if (n->GetType() == wxXML_ELEMENT_NODE)
        {
            ExtractedStrings a2 = FindStrings(filename, n);
            WX_APPEND_ARRAY(arr, a2);
        }

        n = n->GetNext();
    }
    return arr;
}


bool XmlResApp::Validate()
{
    if ( flagVerbose )
        wxPuts("validating XRC files...");

    wxString schemaURI;

    if ( !parSchemaFile.empty() )
    {
        schemaURI = parSchemaFile;
    }
    else
    {
        schemaURI = "http://www.wxwidgets.org/wxxrc";

        // Normally, we'd use an OASIS XML catalog to map the URI to a local copy,
        // but Jing's catalog support (-C catalogFile) requires additional
        // dependency, resolver.jar, that is not commonly installed alongside Jing
        // by systems that package Jing. So do the (trivial) mapping manually here:
        wxString wxWinRoot;
        if ( wxGetEnv("WXWIN", &wxWinRoot) )
        {
            wxString schemaFile(wxWinRoot + "/misc/schema/xrc_schema.rnc");
            if ( wxFileExists(schemaFile) )
                schemaURI = schemaFile;
        }
    }

    wxString cmdline = wxString::Format("jing -c \"%s\"", schemaURI);
    for ( size_t i = 0; i < parFiles.GetCount(); i++ )
        cmdline << wxString::Format(" \"%s\"", parFiles[i]);

    int res = wxExecute(cmdline, wxEXEC_BLOCK);
    if (res == -1)
    {
        wxLogError("Running RELAX NG validator failed.");
        wxLogError("Please install Jing (http://www.thaiopensource.com/relaxng/jing.html).");
        wxLogError("See https://github.com/wxWidgets/wxWidgets/blob/master/misc/schema/README for more information.");
        return false;
    }

    if ( flagVerbose )
    {
        if ( res == 0 )
            wxPuts("XRC validation passed without errors.");
        else
            wxPuts("XRC validation failed, there are errors.");
    }

    return res == 0;
}
