Merge topic 'cpack-dmg-multilanguage-sla'
f88533cc
CPackDMG: Add support for multilingual SLAs
This commit is contained in:
commit
0ecb5e7f8b
|
@ -0,0 +1,7 @@
|
||||||
|
cpack-dmg-multilanguage-sla
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
* The :module:`CPack DragNDrop generator <CPackDMG>` learned to add
|
||||||
|
multi-lingual SLAs to a DMG which is presented to the user when they try to
|
||||||
|
mount the DMG. See the :variable:`CPACK_DMG_SLA_LANGUAGES` and
|
||||||
|
:variable:`CPACK_DMG_SLA_DIR` variables for details.
|
|
@ -36,6 +36,19 @@
|
||||||
# background image is set. The background image is applied after applying the
|
# background image is set. The background image is applied after applying the
|
||||||
# custom .DS_Store file.
|
# custom .DS_Store file.
|
||||||
#
|
#
|
||||||
|
# .. variable:: CPACK_DMG_SLA_DIR
|
||||||
|
#
|
||||||
|
# Directory where license and menu files for different languages are stored.
|
||||||
|
#
|
||||||
|
# .. variable:: CPACK_DMG_SLA_LANGUAGES
|
||||||
|
#
|
||||||
|
# Languages for which a license agreement is provided when mounting the
|
||||||
|
# generated DMG.
|
||||||
|
#
|
||||||
|
# For every language in this list, CPack will try to find files
|
||||||
|
# ``<language>.menu.txt`` and ``<language>.license.txt`` in the directory
|
||||||
|
# specified by the :variable:`CPACK_DMG_SLA_DIR` variable.
|
||||||
|
#
|
||||||
# .. variable:: CPACK_COMMAND_HDIUTIL
|
# .. variable:: CPACK_COMMAND_HDIUTIL
|
||||||
#
|
#
|
||||||
# Path to the hdiutil(1) command used to operate on disk image files on Mac
|
# Path to the hdiutil(1) command used to operate on disk image files on Mac
|
||||||
|
|
|
@ -726,6 +726,9 @@ endif()
|
||||||
# Build CPackLib
|
# Build CPackLib
|
||||||
add_library(CPackLib ${CPACK_SRCS})
|
add_library(CPackLib ${CPACK_SRCS})
|
||||||
target_link_libraries(CPackLib CMakeLib)
|
target_link_libraries(CPackLib CMakeLib)
|
||||||
|
if(APPLE)
|
||||||
|
target_link_libraries(CPackLib "-framework Carbon")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
add_executable(cmakexbuild cmakexbuild.cxx)
|
add_executable(cmakexbuild cmakexbuild.cxx)
|
||||||
|
|
|
@ -18,6 +18,24 @@
|
||||||
#include <cmsys/RegularExpression.hxx>
|
#include <cmsys/RegularExpression.hxx>
|
||||||
#include <cmsys/FStream.hxx>
|
#include <cmsys/FStream.hxx>
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include <CoreFoundation/CFBase.h>
|
||||||
|
#include <CoreFoundation/CFString.h>
|
||||||
|
#include <CoreFoundation/CFLocale.h>
|
||||||
|
|
||||||
|
// The carbon framework is deprecated, but the Region codes it supplies are
|
||||||
|
// needed for the LPic data structure used for generating multi-lingual SLAs.
|
||||||
|
// There does not seem to be a replacement API for these region codes.
|
||||||
|
#if defined(__clang__)
|
||||||
|
# pragma clang diagnostic push
|
||||||
|
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
#endif
|
||||||
|
#include <Carbon/Carbon.h>
|
||||||
|
#if defined(__clang__)
|
||||||
|
# pragma clang diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
static const char* SLAHeader =
|
static const char* SLAHeader =
|
||||||
"data 'LPic' (5000) {\n"
|
"data 'LPic' (5000) {\n"
|
||||||
" $\"0002 0011 0003 0001 0000 0000 0002 0000\"\n"
|
" $\"0002 0011 0003 0001 0000 0000 0002 0000\"\n"
|
||||||
|
@ -103,6 +121,69 @@ int cmCPackDragNDropGenerator::InitializeInternal()
|
||||||
}
|
}
|
||||||
this->SetOptionIfNotSet("CPACK_COMMAND_REZ", rez_path.c_str());
|
this->SetOptionIfNotSet("CPACK_COMMAND_REZ", rez_path.c_str());
|
||||||
|
|
||||||
|
if(this->IsSet("CPACK_DMG_SLA_DIR"))
|
||||||
|
{
|
||||||
|
slaDirectory = this->GetOption("CPACK_DMG_SLA_DIR");
|
||||||
|
if(!slaDirectory.empty() && this->IsSet("CPACK_RESOURCE_FILE_LICENSE"))
|
||||||
|
{
|
||||||
|
std::string license_file =
|
||||||
|
this->GetOption("CPACK_RESOURCE_FILE_LICENSE");
|
||||||
|
if(!license_file.empty() &&
|
||||||
|
(license_file.find("CPack.GenericLicense.txt") == std::string::npos))
|
||||||
|
{
|
||||||
|
cmCPackLogger(cmCPackLog::LOG_WARNING,
|
||||||
|
"Both CPACK_DMG_SLA_DIR and CPACK_RESOURCE_FILE_LICENSE specified, "
|
||||||
|
"defaulting to CPACK_DMG_SLA_DIR"
|
||||||
|
<< std::endl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!this->IsSet("CPACK_DMG_LANGUAGES"))
|
||||||
|
{
|
||||||
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||||
|
"CPACK_DMG_SLA_DIR set but no languages defined "
|
||||||
|
"(set CPACK_DMG_LANGUAGES)"
|
||||||
|
<< std::endl);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(!cmSystemTools::FileExists(slaDirectory, false))
|
||||||
|
{
|
||||||
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||||
|
"CPACK_DMG_SLA_DIR does not exist"
|
||||||
|
<< std::endl);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> languages;
|
||||||
|
cmSystemTools::ExpandListArgument(this->GetOption("CPACK_DMG_LANGUAGES"),
|
||||||
|
languages);
|
||||||
|
if(languages.empty())
|
||||||
|
{
|
||||||
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||||
|
"CPACK_DMG_LANGUAGES set but empty"
|
||||||
|
<< std::endl);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for(size_t i = 0; i < languages.size(); ++i)
|
||||||
|
{
|
||||||
|
std::string license = slaDirectory + "/" + languages[i] + ".license.txt";
|
||||||
|
if (!cmSystemTools::FileExists(license))
|
||||||
|
{
|
||||||
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||||
|
"Missing license file " << languages[i] << ".license.txt"
|
||||||
|
<< std::endl);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::string menu = slaDirectory + "/" + languages[i] + ".menu.txt";
|
||||||
|
if (!cmSystemTools::FileExists(menu))
|
||||||
|
{
|
||||||
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||||
|
"Missing menu file " << languages[i] << ".menu.txt"
|
||||||
|
<< std::endl);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this->Superclass::InitializeInternal();
|
return this->Superclass::InitializeInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,12 +327,23 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
|
||||||
this->GetOption("CPACK_DMG_DS_STORE")
|
this->GetOption("CPACK_DMG_DS_STORE")
|
||||||
? this->GetOption("CPACK_DMG_DS_STORE") : "";
|
? this->GetOption("CPACK_DMG_DS_STORE") : "";
|
||||||
|
|
||||||
|
const std::string cpack_dmg_languages =
|
||||||
|
this->GetOption("CPACK_DMG_LANGUAGES")
|
||||||
|
? this->GetOption("CPACK_DMG_LANGUAGES") : "";
|
||||||
|
|
||||||
// only put license on dmg if is user provided
|
// only put license on dmg if is user provided
|
||||||
if(!cpack_license_file.empty() &&
|
if(!cpack_license_file.empty() &&
|
||||||
cpack_license_file.find("CPack.GenericLicense.txt") != std::string::npos)
|
cpack_license_file.find("CPack.GenericLicense.txt") != std::string::npos)
|
||||||
{
|
{
|
||||||
cpack_license_file = "";
|
cpack_license_file = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use sla_dir if both sla_dir and license_file are set
|
||||||
|
if(!cpack_license_file.empty() &&
|
||||||
|
!slaDirectory.empty())
|
||||||
|
{
|
||||||
|
cpack_license_file = "";
|
||||||
|
}
|
||||||
|
|
||||||
// The staging directory contains everything that will end-up inside the
|
// The staging directory contains everything that will end-up inside the
|
||||||
// final disk image ...
|
// final disk image ...
|
||||||
|
@ -418,54 +510,122 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!cpack_license_file.empty())
|
if(!cpack_license_file.empty() || !slaDirectory.empty())
|
||||||
{
|
{
|
||||||
|
// Use old hardcoded style if sla_dir is not set
|
||||||
|
bool oldStyle = slaDirectory.empty();
|
||||||
std::string sla_r = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
|
std::string sla_r = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
|
||||||
sla_r += "/sla.r";
|
sla_r += "/sla.r";
|
||||||
|
|
||||||
cmsys::ifstream ifs;
|
std::vector<std::string> languages;
|
||||||
ifs.open(cpack_license_file.c_str());
|
if(!oldStyle)
|
||||||
if(ifs.is_open())
|
|
||||||
{
|
|
||||||
cmGeneratedFileStream osf(sla_r.c_str());
|
|
||||||
osf << "#include <CoreServices/CoreServices.r>\n\n";
|
|
||||||
osf << SLAHeader;
|
|
||||||
osf << "\n";
|
|
||||||
osf << "data 'TEXT' (5002, \"English\") {\n";
|
|
||||||
while(ifs.good())
|
|
||||||
{
|
{
|
||||||
std::string line;
|
cmSystemTools::ExpandListArgument(cpack_dmg_languages, languages);
|
||||||
std::getline(ifs, line);
|
}
|
||||||
// escape quotes
|
|
||||||
std::string::size_type pos = line.find('\"');
|
cmGeneratedFileStream ofs(sla_r.c_str());
|
||||||
while(pos != std::string::npos)
|
ofs << "#include <CoreServices/CoreServices.r>\n\n";
|
||||||
|
if(oldStyle)
|
||||||
|
{
|
||||||
|
ofs << SLAHeader;
|
||||||
|
ofs << "\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* LPic Layout
|
||||||
|
* (https://github.com/pypt/dmg-add-license/blob/master/main.c)
|
||||||
|
* as far as I can tell (no official documentation seems to exist):
|
||||||
|
* struct LPic {
|
||||||
|
* uint16_t default_language; // points to a resid, defaulting to 0,
|
||||||
|
* // which is the first set language
|
||||||
|
* uint16_t length;
|
||||||
|
* struct {
|
||||||
|
* uint16_t language_code;
|
||||||
|
* uint16_t resid;
|
||||||
|
* uint16_t encoding; // Encoding from TextCommon.h,
|
||||||
|
* // forcing MacRoman (0) for now. Might need to
|
||||||
|
* // allow overwrite per license by user later
|
||||||
|
* } item[1];
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Create vector first for readability, then iterate to write to ofs
|
||||||
|
std::vector<uint16_t> header_data;
|
||||||
|
header_data.push_back(0);
|
||||||
|
header_data.push_back(languages.size());
|
||||||
|
for(size_t i = 0; i < languages.size(); ++i)
|
||||||
{
|
{
|
||||||
line.replace(pos, 1, "\\\"");
|
CFStringRef language_cfstring = CFStringCreateWithCString(
|
||||||
pos = line.find('\"', pos+2);
|
NULL, languages[i].c_str(), kCFStringEncodingUTF8);
|
||||||
}
|
CFStringRef iso_language =
|
||||||
// break up long lines to avoid Rez errors
|
CFLocaleCreateCanonicalLanguageIdentifierFromString(
|
||||||
std::vector<std::string> lines;
|
NULL, language_cfstring);
|
||||||
const size_t max_line_length = 512;
|
if (!iso_language)
|
||||||
for(size_t i=0; i<line.size(); i+= max_line_length)
|
|
||||||
{
|
{
|
||||||
int line_length = max_line_length;
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||||
if(i+max_line_length > line.size())
|
languages[i] << " is not a recognized language"
|
||||||
line_length = line.size()-i;
|
<< std::endl);
|
||||||
lines.push_back(line.substr(i, line_length));
|
}
|
||||||
|
char *iso_language_cstr = (char *) malloc(65);
|
||||||
|
CFStringGetCString(iso_language, iso_language_cstr, 64,
|
||||||
|
kCFStringEncodingMacRoman);
|
||||||
|
LangCode lang = 0;
|
||||||
|
RegionCode region = 0;
|
||||||
|
OSStatus err = LocaleStringToLangAndRegionCodes(iso_language_cstr,
|
||||||
|
&lang, ®ion);
|
||||||
|
if (err != noErr)
|
||||||
|
{
|
||||||
|
cmCPackLogger(cmCPackLog::LOG_ERROR,
|
||||||
|
"No language/region code available for " << iso_language_cstr
|
||||||
|
<< std::endl);
|
||||||
|
free(iso_language_cstr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
free(iso_language_cstr);
|
||||||
|
header_data.push_back(region);
|
||||||
|
header_data.push_back(i);
|
||||||
|
header_data.push_back(0);
|
||||||
|
}
|
||||||
|
ofs << "data 'LPic' (5000) {\n";
|
||||||
|
ofs << std::hex << std::uppercase << std::setfill('0');
|
||||||
|
|
||||||
|
for(size_t i = 0; i < header_data.size(); ++i)
|
||||||
|
{
|
||||||
|
if(i % 8 == 0)
|
||||||
|
{
|
||||||
|
ofs << " $\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
for(size_t i=0; i<lines.size(); i++)
|
ofs << std::setw(4) << header_data[i];
|
||||||
|
|
||||||
|
if(i % 8 == 7 || i == header_data.size() - 1)
|
||||||
{
|
{
|
||||||
osf << " \"" << lines[i] << "\"\n";
|
ofs << "\"\n";
|
||||||
}
|
}
|
||||||
osf << " \"\\n\"\n";
|
else
|
||||||
|
{
|
||||||
|
ofs << " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ofs << "};\n\n";
|
||||||
|
// Reset ofs options
|
||||||
|
ofs << std::dec << std::nouppercase << std::setfill(' ');
|
||||||
}
|
}
|
||||||
osf << "};\n";
|
|
||||||
osf << "\n";
|
if(oldStyle)
|
||||||
osf << SLASTREnglish;
|
{
|
||||||
ifs.close();
|
WriteLicense(ofs, 0, "", cpack_license_file);
|
||||||
osf.close();
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
for(size_t i = 0; i < languages.size(); ++i)
|
||||||
|
{
|
||||||
|
WriteLicense(ofs, i + 5000, languages[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ofs.Close();
|
||||||
|
|
||||||
// convert to UDCO
|
// convert to UDCO
|
||||||
std::string temp_udco = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
|
std::string temp_udco = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
|
||||||
|
@ -539,7 +699,7 @@ int cmCPackDragNDropGenerator::CreateDMG(const std::string& src_dir,
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_image = temp_udco;
|
temp_image = temp_udco;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create the final compressed read-only disk image ...
|
// Create the final compressed read-only disk image ...
|
||||||
|
@ -607,3 +767,126 @@ cmCPackDragNDropGenerator::GetComponentInstallDirNameSuffix(
|
||||||
|
|
||||||
return GetComponentPackageFileName(package_file_name, componentName, false);
|
return GetComponentPackageFileName(package_file_name, componentName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cmCPackDragNDropGenerator::WriteLicense(cmGeneratedFileStream& outputStream,
|
||||||
|
int licenseNumber, std::string licenseLanguage, std::string licenseFile)
|
||||||
|
{
|
||||||
|
if(!licenseFile.empty())
|
||||||
|
{
|
||||||
|
licenseNumber = 5002;
|
||||||
|
licenseLanguage = "English";
|
||||||
|
}
|
||||||
|
|
||||||
|
// License header
|
||||||
|
outputStream << "data 'TEXT' (" << licenseNumber << ", \""
|
||||||
|
<< licenseLanguage << "\") {\n";
|
||||||
|
// License body
|
||||||
|
std::string actual_license = !licenseFile.empty() ? licenseFile :
|
||||||
|
(slaDirectory + "/" + licenseLanguage + ".license.txt");
|
||||||
|
cmsys::ifstream license_ifs;
|
||||||
|
license_ifs.open(actual_license.c_str());
|
||||||
|
if(license_ifs.is_open())
|
||||||
|
{
|
||||||
|
while(license_ifs.good())
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
std::getline(license_ifs, line);
|
||||||
|
if(!line.empty())
|
||||||
|
{
|
||||||
|
EscapeQuotes(line);
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
BreakLongLine(line, lines);
|
||||||
|
for(size_t i = 0; i < lines.size(); ++i)
|
||||||
|
{
|
||||||
|
outputStream << " \"" << lines[i] << "\"\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputStream << " \"\\n\"\n";
|
||||||
|
}
|
||||||
|
license_ifs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of License
|
||||||
|
outputStream << "};\n\n";
|
||||||
|
if(!licenseFile.empty())
|
||||||
|
{
|
||||||
|
outputStream << SLASTREnglish;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Menu header
|
||||||
|
outputStream << "resource 'STR#' (" << licenseNumber << ", \""
|
||||||
|
<< licenseLanguage << "\") {\n";
|
||||||
|
outputStream << " {\n";
|
||||||
|
|
||||||
|
// Menu body
|
||||||
|
cmsys::ifstream menu_ifs;
|
||||||
|
menu_ifs.open((slaDirectory+"/"+licenseLanguage+".menu.txt").c_str());
|
||||||
|
if(menu_ifs.is_open())
|
||||||
|
{
|
||||||
|
size_t lines_written = 0;
|
||||||
|
while(menu_ifs.good())
|
||||||
|
{
|
||||||
|
// Lines written from original file, not from broken up lines
|
||||||
|
std::string line;
|
||||||
|
std::getline(menu_ifs, line);
|
||||||
|
if(!line.empty())
|
||||||
|
{
|
||||||
|
EscapeQuotes(line);
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
BreakLongLine(line, lines);
|
||||||
|
for(size_t i = 0; i < lines.size(); ++i)
|
||||||
|
{
|
||||||
|
std::string comma;
|
||||||
|
// We need a comma after every complete string,
|
||||||
|
// but not on the very last line
|
||||||
|
if(lines_written != 8 && i == lines.size() - 1)
|
||||||
|
{
|
||||||
|
comma = ",";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
comma = "";
|
||||||
|
}
|
||||||
|
outputStream << " \"" << lines[i] << "\"" << comma << "\n";
|
||||||
|
}
|
||||||
|
++lines_written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menu_ifs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
//End of menu
|
||||||
|
outputStream << " }\n";
|
||||||
|
outputStream << "};\n";
|
||||||
|
outputStream << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cmCPackDragNDropGenerator::BreakLongLine(const std::string& line,
|
||||||
|
std::vector<std::string>& lines)
|
||||||
|
{
|
||||||
|
const size_t max_line_length = 512;
|
||||||
|
for(size_t i = 0; i < line.size(); i += max_line_length)
|
||||||
|
{
|
||||||
|
int line_length = max_line_length;
|
||||||
|
if(i + max_line_length > line.size())
|
||||||
|
{
|
||||||
|
line_length = line.size() - i;
|
||||||
|
}
|
||||||
|
lines.push_back(line.substr(i, line_length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cmCPackDragNDropGenerator::EscapeQuotes(std::string& line)
|
||||||
|
{
|
||||||
|
std::string::size_type pos = line.find('\"');
|
||||||
|
while(pos != std::string::npos)
|
||||||
|
{
|
||||||
|
line.replace(pos, 1, "\\\"");
|
||||||
|
pos = line.find('\"', pos + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
#include "cmCPackGenerator.h"
|
#include "cmCPackGenerator.h"
|
||||||
|
|
||||||
|
class cmGeneratedFileStream;
|
||||||
|
|
||||||
/** \class cmCPackDragNDropGenerator
|
/** \class cmCPackDragNDropGenerator
|
||||||
* \brief A generator for OSX drag-n-drop installs
|
* \brief A generator for OSX drag-n-drop installs
|
||||||
*/
|
*/
|
||||||
|
@ -42,6 +44,15 @@ protected:
|
||||||
int CreateDMG(const std::string& src_dir, const std::string& output_file);
|
int CreateDMG(const std::string& src_dir, const std::string& output_file);
|
||||||
|
|
||||||
std::string InstallPrefix;
|
std::string InstallPrefix;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string slaDirectory;
|
||||||
|
|
||||||
|
void WriteLicense(cmGeneratedFileStream& outputStream, int licenseNumber,
|
||||||
|
std::string licenseLanguage, std::string licenseFile = "");
|
||||||
|
void BreakLongLine(const std::string& line,
|
||||||
|
std::vector<std::string>& lines);
|
||||||
|
void EscapeQuotes(std::string& line);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue