You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
705 lines
20 KiB
C++
705 lines
20 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
// Original file Copyright Crytek GMBH or its affiliates, used under license.
|
|
|
|
#include "ResourceCompilerXML_precompiled.h"
|
|
#include "XMLConverter.h"
|
|
#include "IRCLog.h"
|
|
#include "IXMLSerializer.h"
|
|
#include "XMLBinaryHeaders.h"
|
|
#include <CrySystem/XML/XMLBinaryReader.h>
|
|
#include <CrySystem/XML/XMLBinaryWriter.h>
|
|
#include "IConfig.h"
|
|
#include "IResCompiler.h"
|
|
#include "FileUtil.h"
|
|
#include "StringHelpers.h"
|
|
#include "UpToDateFileHelpers.h"
|
|
|
|
#include <QDir>
|
|
|
|
XMLCompiler::XMLCompiler(
|
|
ICryXML* pCryXML,
|
|
const std::vector<XMLFilterElement>& filter,
|
|
const std::vector<string>& tableFilemasks,
|
|
const NameConvertor& nameConverter)
|
|
: m_refCount(1)
|
|
, m_pCryXML(pCryXML)
|
|
, m_pFilter(&filter)
|
|
, m_pTableFilemasks(&tableFilemasks)
|
|
, m_pNameConverter(&nameConverter)
|
|
{
|
|
}
|
|
|
|
void XMLCompiler::Release()
|
|
{
|
|
if (--m_refCount <= 0)
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
XMLConverter::XMLConverter(ICryXML* pCryXML)
|
|
: m_pCryXML(pCryXML)
|
|
, m_refCount(1)
|
|
{
|
|
m_pCryXML->AddRef();
|
|
}
|
|
|
|
XMLConverter::~XMLConverter()
|
|
{
|
|
m_pCryXML->Release();
|
|
}
|
|
|
|
void XMLConverter::Release()
|
|
{
|
|
if (--m_refCount <= 0)
|
|
{
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
void XMLConverter::Init(const ConvertorInitContext& context)
|
|
{
|
|
m_filter.clear();
|
|
m_tableFilemasks.clear();
|
|
|
|
if (!m_nameConvertor.SetRules(context.config->GetAsString("targetnameformat", "", "")))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const string xmlFilterFile = context.config->GetAsString("xmlFilterFile", "", "");
|
|
if (xmlFilterFile.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FILE* f = nullptr;
|
|
azfopen(&f, xmlFilterFile.c_str(), "rt");
|
|
if (!f)
|
|
{
|
|
RCLogError("XML: Failed to open XML filter file \"%s\"", xmlFilterFile.c_str());
|
|
return;
|
|
}
|
|
|
|
char line[1024];
|
|
while (fgets(line, sizeof(line), f) != NULL)
|
|
{
|
|
if (line[0] == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string sLine = line;
|
|
sLine.Trim();
|
|
if (sLine.empty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (::tolower(sLine[0]) == 'f')
|
|
{
|
|
sLine = sLine.substr(1, sLine.npos);
|
|
sLine.TrimLeft();
|
|
const string sKeywordTable = "table";
|
|
if (StringHelpers::StartsWithIgnoreCase(sLine, sKeywordTable))
|
|
{
|
|
sLine.erase(0, sKeywordTable.length());
|
|
sLine.TrimLeft();
|
|
if (!sLine.empty())
|
|
{
|
|
m_tableFilemasks.push_back(PathHelpers::ToDosPath(sLine));
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ((::tolower(sLine[0]) == 'a' || ::tolower(sLine[0]) == 'e'))
|
|
{
|
|
XMLFilterElement fe;
|
|
fe.type = (::tolower(sLine[0]) == 'a')
|
|
? XMLBinary::IFilter::eType_AttributeName
|
|
: XMLBinary::IFilter::eType_ElementName;
|
|
sLine = sLine.substr(1, sLine.npos);
|
|
sLine.TrimLeft();
|
|
if (!sLine.empty() && (sLine[0] == '+' || sLine[0] == '-'))
|
|
{
|
|
fe.bAccept = (sLine[0] == '+');
|
|
sLine = sLine.substr(1, sLine.npos);
|
|
sLine.TrimLeft();
|
|
if (!sLine.empty())
|
|
{
|
|
fe.wildcards = sLine;
|
|
m_filter.push_back(fe);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
class CXmlBinaryDataWriterFile
|
|
: public XMLBinary::IDataWriter
|
|
{
|
|
public:
|
|
CXmlBinaryDataWriterFile(const char* file)
|
|
{
|
|
m_file = nullptr;
|
|
azfopen(&m_file, file, "wb");
|
|
}
|
|
~CXmlBinaryDataWriterFile()
|
|
{
|
|
if (m_file)
|
|
{
|
|
fclose(m_file);
|
|
}
|
|
}
|
|
virtual bool IsOk()
|
|
{
|
|
return m_file != 0;
|
|
}
|
|
virtual void Write(const void* pData, size_t size)
|
|
{
|
|
if (m_file)
|
|
{
|
|
fwrite(pData, size, 1, m_file);
|
|
}
|
|
}
|
|
private:
|
|
FILE* m_file;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
class CXmlFilter
|
|
: public XMLBinary::IFilter
|
|
{
|
|
public:
|
|
CXmlFilter(const std::vector<XMLFilterElement>* pFilterArray)
|
|
: m_pFilterArray((pFilterArray && !pFilterArray->empty()) ? pFilterArray : 0)
|
|
{
|
|
}
|
|
|
|
virtual bool IsAccepted(EType type, const char* pName) const
|
|
{
|
|
if (!m_pFilterArray)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const string name(pName);
|
|
for (size_t i = 0; i < m_pFilterArray->size(); ++i)
|
|
{
|
|
if (((*m_pFilterArray)[i].type == type) &&
|
|
StringHelpers::MatchesWildcardsIgnoreCase(name, (*m_pFilterArray)[i].wildcards))
|
|
{
|
|
return (*m_pFilterArray)[i].bAccept;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const std::vector<XMLFilterElement>* m_pFilterArray;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
static bool xmlsAreEqual(XmlNodeRef node0, XmlNodeRef node1, XMLBinary::IFilter* pFilter, const char*& mismatchInfo)
|
|
{
|
|
mismatchInfo = 0;
|
|
|
|
{
|
|
const char* const tag0 = node0->getTag();
|
|
const char* const tag1 = node1->getTag();
|
|
|
|
if (strcmp(tag0, tag1) != 0)
|
|
{
|
|
mismatchInfo = "tags";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
{
|
|
const char* const content0 = node0->getContent();
|
|
const char* const content1 = node1->getContent();
|
|
|
|
if (strcmp(content0, content1) != 0)
|
|
{
|
|
mismatchInfo = "content";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
{
|
|
const int count0 = node0->getNumAttributes();
|
|
const int count1 = node1->getNumAttributes();
|
|
int index1 = 0;
|
|
int filteredCount1 = 0;
|
|
|
|
for (int index0 = 0; index0 < count0; ++index0)
|
|
{
|
|
const char* key0;
|
|
const char* value0;
|
|
node0->getAttributeByIndex(index0, &key0, &value0);
|
|
|
|
const char* key1;
|
|
const char* value1;
|
|
for (;; )
|
|
{
|
|
if (index1 >= count1)
|
|
{
|
|
mismatchInfo = "attribute count";
|
|
return false;
|
|
}
|
|
node1->getAttributeByIndex(index1++, &key1, &value1);
|
|
if (!pFilter || pFilter->IsAccepted(XMLBinary::IFilter::eType_AttributeName, key1))
|
|
{
|
|
++filteredCount1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (strcmp(key0, key1) != 0)
|
|
{
|
|
mismatchInfo = "attribute name";
|
|
return false;
|
|
}
|
|
|
|
if (strcmp(value0, value1) != 0)
|
|
{
|
|
mismatchInfo = "attribute value";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (count0 != filteredCount1)
|
|
{
|
|
mismatchInfo = "attribute count";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
{
|
|
const int count0 = node0->getChildCount();
|
|
const int count1 = node1->getChildCount();
|
|
int index1 = 0;
|
|
int filteredCount1 = 0;
|
|
|
|
for (int index0 = 0; index0 < count0; ++index0)
|
|
{
|
|
XmlNodeRef child0 = node0->getChild(index0);
|
|
|
|
XmlNodeRef child1;
|
|
for (;; )
|
|
{
|
|
if (index1 >= count1)
|
|
{
|
|
mismatchInfo = "child count";
|
|
return false;
|
|
}
|
|
child1 = node1->getChild(index1++);
|
|
if (!pFilter || pFilter->IsAccepted(XMLBinary::IFilter::eType_ElementName, child1->getTag()))
|
|
{
|
|
++filteredCount1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const bool bEqual = xmlsAreEqual(child0, child1, pFilter, mismatchInfo);
|
|
if (!bEqual)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (count0 != filteredCount1)
|
|
{
|
|
mismatchInfo = "child count";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static string GetNormalizedFullPath(const string& relativePath)
|
|
{
|
|
QFileInfo file(relativePath.c_str());
|
|
return file.absoluteFilePath().toStdString().c_str();
|
|
}
|
|
|
|
static XmlNodeRef ConvertFromExcelXmlToCryEngineTableXml(XmlNodeRef root, IXMLSerializer* pSerializer, const string& sInputFile)
|
|
{
|
|
XmlNodeRef nodeTable;
|
|
|
|
{
|
|
const char* err = 0;
|
|
XmlNodeRef nodeWorksheet = root->findChild("Worksheet");
|
|
if (!nodeWorksheet)
|
|
{
|
|
err = "element 'Worksheet' is missing";
|
|
}
|
|
else
|
|
{
|
|
nodeTable = nodeWorksheet->findChild("Table");
|
|
if (!nodeTable)
|
|
{
|
|
err = "element 'Table' is missing in 'Worksheet'";
|
|
}
|
|
}
|
|
|
|
if (err)
|
|
{
|
|
RCLogError(
|
|
"XML: File \"%s\" expected to be an Excel spreadsheet XML, but it's not: %s. Check your /xmlfilterfile's file.",
|
|
sInputFile.c_str(), err);
|
|
return XmlNodeRef(0);
|
|
}
|
|
}
|
|
|
|
XmlNodeRef outTable = pSerializer->CreateNode("Table");
|
|
|
|
const bool bKeepEmptyRows = false;
|
|
|
|
const string sNewLine("\n");
|
|
string sRow;
|
|
|
|
int rowIndex = -1;
|
|
for (int row = 0; row < nodeTable->getChildCount(); ++row)
|
|
{
|
|
XmlNodeRef nodeRow = nodeTable->getChild(row);
|
|
if (!nodeRow->isTag("Row"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
++rowIndex;
|
|
|
|
// Handling skipped rows
|
|
{
|
|
int index = 0;
|
|
if (nodeRow->getAttr("ss:Index", index))
|
|
{
|
|
--index; // one-based -> zero-based
|
|
if (index < rowIndex)
|
|
{
|
|
RCLogError("XML: \"%s\": ss:Index has unexpected value %d", sInputFile.c_str(), index + 1);
|
|
return XmlNodeRef(0);
|
|
}
|
|
if (bKeepEmptyRows)
|
|
{
|
|
while (rowIndex < index)
|
|
{
|
|
XmlNodeRef outRow = pSerializer->CreateNode("Row");
|
|
outTable->addChild(outRow);
|
|
++rowIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rowIndex = index;
|
|
}
|
|
}
|
|
}
|
|
|
|
sRow.clear();
|
|
|
|
int cellIndex = -1;
|
|
for (int cell = 0; cell < nodeRow->getChildCount(); ++cell)
|
|
{
|
|
XmlNodeRef nodeCell = nodeRow->getChild(cell);
|
|
if (!nodeCell->isTag("Cell"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
++cellIndex;
|
|
|
|
// Handling skipped columns
|
|
{
|
|
int index = 0;
|
|
if (nodeCell->getAttr("ss:Index", index))
|
|
{
|
|
--index; // one-based -> zero-based
|
|
if (index < cellIndex)
|
|
{
|
|
RCLogError("XML: \"%s\": ss:Index has unexpected value %d", sInputFile.c_str(), index + 1);
|
|
return XmlNodeRef(0);
|
|
}
|
|
while (cellIndex < index)
|
|
{
|
|
sRow.append(sNewLine);
|
|
++cellIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
XmlNodeRef nodeCellData = nodeCell->findChild("Data");
|
|
if (nodeCellData)
|
|
{
|
|
const char* const pContent = nodeCellData->getContent();
|
|
if (pContent)
|
|
{
|
|
sRow.append(pContent);
|
|
}
|
|
}
|
|
sRow.append(sNewLine);
|
|
}
|
|
|
|
// Erase empty trailing cells in the row
|
|
while (StringHelpers::EndsWith(sRow, sNewLine))
|
|
{
|
|
sRow.erase(sRow.length() - sNewLine.length());
|
|
}
|
|
|
|
if (!sRow.empty() || bKeepEmptyRows)
|
|
{
|
|
XmlNodeRef outRow = pSerializer->CreateNode("Row");
|
|
outRow->setContent(sRow.c_str());
|
|
outTable->addChild(outRow);
|
|
}
|
|
}
|
|
|
|
XmlNodeRef outRoot = pSerializer->CreateNode("Tables");
|
|
outRoot->addChild(outTable);
|
|
|
|
if (false)
|
|
{
|
|
const string sFilename = sInputFile + ".debug.xml";
|
|
const bool ok = pSerializer->Write(outRoot, sFilename.c_str());
|
|
if (!ok)
|
|
{
|
|
RCLogError("XML: Failed to write XML file \"%s\".", sFilename.c_str());
|
|
return XmlNodeRef(0);
|
|
}
|
|
}
|
|
|
|
return outRoot;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool XMLCompiler::Process()
|
|
{
|
|
const int verbosityLevel = m_CC.m_pRC->GetVerbosityLevel();
|
|
|
|
const bool bNeedSwapEndian = m_CC.m_pRC->GetPlatformInfo(m_CC.m_platform)->bBigEndian;
|
|
if (verbosityLevel >= 1 && bNeedSwapEndian)
|
|
{
|
|
RCLog("XML: Endian conversion specified");
|
|
}
|
|
|
|
// Get the files to process.
|
|
const string sInputFile = m_CC.GetSourcePath();
|
|
string sOutputFile = GetOutputPath();
|
|
if (m_pNameConverter->HasRules())
|
|
{
|
|
const string oldFilename = PathHelpers::GetFilename(sOutputFile);
|
|
const string newFilename = m_pNameConverter->GetConvertedName(oldFilename);
|
|
if (newFilename.empty())
|
|
{
|
|
return false;
|
|
}
|
|
if (!StringHelpers::EqualsIgnoreCase(oldFilename, newFilename))
|
|
{
|
|
if (verbosityLevel >= 2)
|
|
{
|
|
RCLog("Target file name changed: %s -> %s", oldFilename.c_str(), newFilename.c_str());
|
|
}
|
|
sOutputFile = PathHelpers::Join(PathHelpers::GetDirectory(sOutputFile), newFilename);
|
|
}
|
|
}
|
|
|
|
// Check that we will not overwrite source file
|
|
{
|
|
const string fname0 = GetNormalizedFullPath(sInputFile);
|
|
const string fname1 = GetNormalizedFullPath(sOutputFile);
|
|
|
|
if (fname0.empty() || fname1.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StringHelpers::EqualsIgnoreCase(fname0, fname1))
|
|
{
|
|
RCLogError("XML: Source file cannot be same as target file. Use /targetroot=... option.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!m_CC.m_bForceRecompiling && UpToDateFileHelpers::FileExistsAndUpToDate(sOutputFile, sInputFile))
|
|
{
|
|
// The file is up-to-date
|
|
m_CC.m_pRC->AddInputOutputFilePair(sInputFile, sOutputFile);
|
|
return true;
|
|
}
|
|
|
|
// Check that the input file exists.
|
|
if (!FileUtil::FileExists(sInputFile.c_str()))
|
|
{
|
|
RCLogError("XML: File \"%s\" does not exist", sInputFile.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Get XML serializer.
|
|
IXMLSerializer* pSerializer = m_pCryXML->GetXMLSerializer();
|
|
|
|
// Ensure that the input file is not in binary XML format.
|
|
{
|
|
XMLBinary::XMLBinaryReader binReader;
|
|
XMLBinary::XMLBinaryReader::EResult binResult;
|
|
XmlNodeRef rootBin = binReader.LoadFromFile(sInputFile.c_str(), binResult);
|
|
if (binResult == XMLBinary::XMLBinaryReader::eResult_Success)
|
|
{
|
|
RCLogError("XML: Source file is binary XML \"%s\"", sInputFile.c_str());
|
|
return false;
|
|
}
|
|
if (binResult == XMLBinary::XMLBinaryReader::eResult_Error)
|
|
{
|
|
RCLogError("XML: Input XML file is either binary or damaged \"%s\": %s", sInputFile.c_str(), binReader.GetErrorDescription());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Read input XML file.
|
|
XmlNodeRef root;
|
|
{
|
|
const bool bRemoveNonessentialSpacesFromContent = true;
|
|
char szErrorBuffer[1024];
|
|
szErrorBuffer[0] = 0;
|
|
root = pSerializer->Read(FileXmlBufferSource(sInputFile.c_str()), bRemoveNonessentialSpacesFromContent, sizeof(szErrorBuffer), szErrorBuffer);
|
|
if (!root)
|
|
{
|
|
const char* const pErrorStr =
|
|
(szErrorBuffer[0])
|
|
? &szErrorBuffer[0]
|
|
: "Probably this file has bad XML syntax or it's not XML file at all";
|
|
RCLogError("XML: Cannot read file \"%s\": %s", sInputFile.c_str(), pErrorStr);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Convert Excel's XML format to CryEngine's table XML format, if requested.
|
|
{
|
|
bool bConvert = false;
|
|
for (size_t i = 0; i < m_pTableFilemasks->size(); ++i)
|
|
{
|
|
if (StringHelpers::MatchesWildcardsIgnoreCase(sInputFile, (*m_pTableFilemasks)[i]))
|
|
{
|
|
bConvert = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bConvert)
|
|
{
|
|
XmlNodeRef rootConverted = ConvertFromExcelXmlToCryEngineTableXml(root, pSerializer, sInputFile);
|
|
if (!rootConverted)
|
|
{
|
|
return false;
|
|
}
|
|
root = rootConverted;
|
|
}
|
|
}
|
|
|
|
// Create filter to get rid of unneeded elements and attributes
|
|
CXmlFilter filter(m_pFilter);
|
|
|
|
// Write out the destination file.
|
|
{
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
SetFileAttributes(sOutputFile.c_str(), FILE_ATTRIBUTE_ARCHIVE);
|
|
#endif
|
|
|
|
FILE* pDestinationFile = nullptr;
|
|
azfopen(&pDestinationFile, sOutputFile.c_str(), "wb");
|
|
if (pDestinationFile == 0)
|
|
{
|
|
char error[1024];
|
|
azstrerror_s(error, AZ_ARRAY_SIZE(error), errno);
|
|
RCLogError("XML: Cannot write file \"%s\": %s", sInputFile.c_str(), error);
|
|
return false;
|
|
}
|
|
fclose(pDestinationFile);
|
|
|
|
CXmlBinaryDataWriterFile outputFile(sOutputFile.c_str());
|
|
XMLBinary::CXMLBinaryWriter xmlBinaryWriter;
|
|
string error;
|
|
const bool ok = xmlBinaryWriter.WriteNode(&outputFile, root, bNeedSwapEndian, &filter, error);
|
|
if (!ok)
|
|
{
|
|
remove(sOutputFile.c_str());
|
|
RCLogError("XML: Failed to write binary XML file \"%s\": %s", sOutputFile.c_str(), error.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Verify that the output file was written
|
|
if (!FileUtil::FileExists(sOutputFile.c_str()))
|
|
{
|
|
RCLogError("XML: Failed to write file \"%s\"", sOutputFile.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Check that the output binary XML file has same content as the input XML
|
|
if (!bNeedSwapEndian)
|
|
{
|
|
XMLBinary::XMLBinaryReader binReader;
|
|
XMLBinary::XMLBinaryReader::EResult binResult;
|
|
XmlNodeRef rootBin = binReader.LoadFromFile(sOutputFile.c_str(), binResult);
|
|
if (!rootBin)
|
|
{
|
|
RCLogError("XML: Cannot read binary XML file \"%s\". Contact RC programmers.", sOutputFile.c_str());
|
|
return false;
|
|
}
|
|
|
|
const char* mismatchInfo = 0;
|
|
const bool bEqual = xmlsAreEqual(rootBin, root, &filter, mismatchInfo);
|
|
if (!bEqual)
|
|
{
|
|
RCLogError(
|
|
"XML: Source XML file \"%s\" and result binary XML file \"%s\" are different: %s. Contact RC programmers.",
|
|
sInputFile.c_str(), sOutputFile.c_str(), mismatchInfo);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!UpToDateFileHelpers::SetMatchingFileTime(sOutputFile, sInputFile))
|
|
{
|
|
return false;
|
|
}
|
|
m_CC.m_pRC->AddInputOutputFilePair(sInputFile, sOutputFile);
|
|
|
|
return true;
|
|
}
|
|
|
|
string XMLCompiler::GetOutputFileNameOnly() const
|
|
{
|
|
return m_CC.m_config->GetAsString("overwritefilename", m_CC.m_sourceFileNameOnly.c_str(), m_CC.m_sourceFileNameOnly.c_str());
|
|
}
|
|
|
|
string XMLCompiler::GetOutputPath() const
|
|
{
|
|
return PathHelpers::Join(m_CC.GetOutputFolder(), GetOutputFileNameOnly());
|
|
}
|
|
|
|
const char* XMLConverter::GetExt(int index) const
|
|
{
|
|
return (index == 0) ? "xml" : 0;
|
|
}
|
|
|
|
ICompiler* XMLConverter::CreateCompiler()
|
|
{
|
|
return new XMLCompiler(m_pCryXML, m_filter, m_tableFilemasks, m_nameConvertor);
|
|
}
|