mirror of
https://develop.openfoam.com/Development/openfoam.git
synced 2025-11-28 03:28:01 +00:00
- Now accept '/' when reading variables without requiring
a surrounding '{}'
- fix some degenerate parsing cases when the first character is
already bad.
Eg, $"abc" would have previously parsed as a <$"> variable, even
although a double quote is not a valid variable character.
Now emits a warning and parses as a '$' token and a string token.
1283 lines
29 KiB
C
1283 lines
29 KiB
C
/*---------------------------------------------------------------------------*\
|
|
========= |
|
|
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
|
|
\\ / O peration |
|
|
\\ / A nd | Copyright (C) 2017-2019 OpenCFD Ltd.
|
|
\\/ M anipulation |
|
|
-------------------------------------------------------------------------------
|
|
| Copyright (C) 2011-2016 OpenFOAM Foundation
|
|
-------------------------------------------------------------------------------
|
|
License
|
|
This file is part of OpenFOAM.
|
|
|
|
OpenFOAM is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
#include "stringOps.H"
|
|
#include "typeInfo.H"
|
|
#include "etcFiles.H"
|
|
#include "Pstream.H"
|
|
#include "StringStream.H"
|
|
#include "OSstream.H"
|
|
#include "OSspecific.H"
|
|
#include <cctype>
|
|
|
|
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
|
|
|
|
namespace Foam
|
|
{
|
|
|
|
// Return the file location mode (string) as a numerical value.
|
|
//
|
|
// - u : location mask 0700
|
|
// - g : location mask 0070
|
|
// - o : location mask 0007
|
|
// - a : location mask 0777
|
|
//
|
|
static inline unsigned short modeToLocation
|
|
(
|
|
const std::string& mode,
|
|
std::size_t pos = 0
|
|
)
|
|
{
|
|
unsigned short where(0);
|
|
|
|
if (std::string::npos != mode.find('u', pos)) { where |= 0700; } // User
|
|
if (std::string::npos != mode.find('g', pos)) { where |= 0070; } // Group
|
|
if (std::string::npos != mode.find('o', pos)) { where |= 0007; } // Other
|
|
if (std::string::npos != mode.find('a', pos)) { where |= 0777; } // All
|
|
|
|
return where;
|
|
}
|
|
|
|
|
|
// Expand a leading <tag>/
|
|
// Convenient for frequently used directories
|
|
//
|
|
// <etc>/ => user/group/other etc - findEtcEntry()
|
|
// <etc(:[ugoa]+)?>/ => user/group/other etc - findEtcEntry()
|
|
// <case>/ => FOAM_CASE directory
|
|
// <constant>/ => FOAM_CASE/constant directory
|
|
// <system>/ => FOAM_CASE/system directory
|
|
static void expandLeadingTag(std::string& s, const char b, const char e)
|
|
{
|
|
if (s[0] != b)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto delim = s.find(e);
|
|
if (std::string::npos == delim)
|
|
{
|
|
return; // Error: no closing delim - ignore expansion
|
|
}
|
|
|
|
fileName file;
|
|
|
|
const char nextC = s[++delim];
|
|
|
|
// Require the following character to be '/' or the end of string.
|
|
if (nextC)
|
|
{
|
|
if (nextC != '/')
|
|
{
|
|
return;
|
|
}
|
|
|
|
file.assign(s.substr(delim + 1));
|
|
}
|
|
|
|
const std::string tag(s, 1, delim-2);
|
|
const auto tagLen = tag.length();
|
|
|
|
// Note that file is also allowed to be an empty string.
|
|
|
|
if (tag == "etc")
|
|
{
|
|
s = findEtcEntry(file);
|
|
}
|
|
else if (tag == "case")
|
|
{
|
|
s = fileName(Foam::getEnv("FOAM_CASE"))/file;
|
|
}
|
|
else if (tag == "constant" || tag == "system")
|
|
{
|
|
s = fileName(Foam::getEnv("FOAM_CASE"))/tag/file;
|
|
}
|
|
else if (tagLen >= 4 && tag.compare(0, 4, "etc:") == 0)
|
|
{
|
|
// <etc:[ugoa]+> type of tag - convert "ugo" to numeric
|
|
|
|
s = findEtcEntry(file, modeToLocation(tag,4));
|
|
}
|
|
}
|
|
|
|
|
|
// Expand a leading tilde
|
|
// ~/ => home directory
|
|
// ~user => home directory for specified user
|
|
// Deprecated ~OpenFOAM => <etc> instead
|
|
static void expandLeadingTilde(std::string& s)
|
|
{
|
|
if (s[0] != '~')
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::string user;
|
|
fileName file;
|
|
|
|
const auto slash = s.find('/');
|
|
if (slash == std::string::npos)
|
|
{
|
|
user = s.substr(1);
|
|
}
|
|
else
|
|
{
|
|
user = s.substr(1, slash - 1);
|
|
file = s.substr(slash + 1);
|
|
}
|
|
|
|
// NB: be a bit lazy and expand ~unknownUser as an
|
|
// empty string rather than leaving it untouched.
|
|
// otherwise add extra test
|
|
|
|
if (user == "OpenFOAM")
|
|
{
|
|
// Compat Warning
|
|
const int version(1806);
|
|
|
|
// Single warning (on master) with guard to avoid Pstream::master()
|
|
// when Pstream has not yet been initialized
|
|
if (Pstream::parRun() ? Pstream::master() : true)
|
|
{
|
|
std::cerr
|
|
<< nl
|
|
<< "--> FOAM Warning :" << nl
|
|
<< " Found [v" << version << "] '"
|
|
<< "~OpenFOAM" << "' string expansion instead of '"
|
|
<< "<etc>" << "' in string\n\"" << s << "\"\n" << nl
|
|
<< std::endl;
|
|
|
|
error::warnAboutAge("expansion", version);
|
|
}
|
|
|
|
s = findEtcFile(file);
|
|
}
|
|
else
|
|
{
|
|
s = home(user)/file;
|
|
}
|
|
}
|
|
|
|
|
|
// Expand leading contents: "./", "~..", "<tag>/"
|
|
static void expandLeading(std::string& s)
|
|
{
|
|
if (s.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (s[0])
|
|
{
|
|
case '.':
|
|
{
|
|
// Expand a lone '.' and an initial './' into cwd
|
|
if (s.size() == 1)
|
|
{
|
|
s = cwd();
|
|
}
|
|
else if (s[1] == '/')
|
|
{
|
|
s.std::string::replace(0, 1, cwd());
|
|
}
|
|
break;
|
|
}
|
|
case '<':
|
|
{
|
|
expandLeadingTag(s, '<', '>');
|
|
break;
|
|
}
|
|
case '~':
|
|
{
|
|
expandLeadingTilde(s);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Serialize an entry (primitive or dictionary) with special treatment
|
|
// for primitive entries that are already a string-type.
|
|
static inline std::string entryToString
|
|
(
|
|
const entry* eptr,
|
|
const bool allowSubDict
|
|
)
|
|
{
|
|
std::string str;
|
|
|
|
if (eptr)
|
|
{
|
|
OStringStream buf;
|
|
// Force floating point numbers to be printed with at least
|
|
// some decimal digits.
|
|
buf << fixed;
|
|
buf.precision(IOstream::defaultPrecision());
|
|
|
|
if (allowSubDict && eptr->isDict())
|
|
{
|
|
eptr->dict().write(buf, false);
|
|
str = buf.str();
|
|
}
|
|
else
|
|
{
|
|
// Fail for non-primitiveEntry
|
|
const primitiveEntry& pe =
|
|
dynamicCast<const primitiveEntry>(*eptr);
|
|
|
|
if (pe.size() == 1 && pe[0].isStringType())
|
|
{
|
|
// Already a string-type. Just copy.
|
|
str = pe[0].stringToken();
|
|
}
|
|
else
|
|
{
|
|
pe.write(buf, true);
|
|
str = buf.str();
|
|
}
|
|
}
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
} // End namespace Foam
|
|
|
|
|
|
// Details for handling dictionary expansion
|
|
|
|
namespace
|
|
{
|
|
|
|
// Acceptable values for $variable names.
|
|
//
|
|
// Similar to word::valid(), except we don't have the benefit of a parser
|
|
// to filter out other unacceptable entries for us.
|
|
//
|
|
// Does not currently accept '/' in a variable name.
|
|
// We would like "$file/$name" to expand as two variables.
|
|
static inline bool validVariableChar(char c)
|
|
{
|
|
return
|
|
(
|
|
std::isalnum(c)
|
|
|| c == '.'
|
|
|| c == ':'
|
|
|| c == '_'
|
|
);
|
|
}
|
|
|
|
|
|
// Find the type/position of the ":-" or ":+" alternative values
|
|
// Returns 0, '-', '+' corresponding to not-found or ':-' or ':+'
|
|
static inline int findParameterAlternative
|
|
(
|
|
const std::string& s,
|
|
std::string::size_type& pos,
|
|
std::string::size_type endPos
|
|
)
|
|
{
|
|
while (pos != std::string::npos)
|
|
{
|
|
pos = s.find(':', pos);
|
|
if (pos != std::string::npos)
|
|
{
|
|
if (pos < endPos)
|
|
{
|
|
// in-range: check for '+' or '-' following the ':'
|
|
const int altType = s[pos+1];
|
|
if (altType == '+' || altType == '-')
|
|
{
|
|
return altType;
|
|
}
|
|
|
|
++pos; // unknown/unsupported - continue at next position
|
|
}
|
|
else
|
|
{
|
|
// out-of-range: abort
|
|
pos = std::string::npos;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// For input string of "$variable with other" return the length of
|
|
// the variable.
|
|
//
|
|
// Intentionally will not capture ':+', ':-' alterations. Use ${ .. } for that
|
|
static inline std::string::size_type findVariableLen
|
|
(
|
|
const std::string& s,
|
|
std::string::size_type pos,
|
|
const char sigil = '$'
|
|
)
|
|
{
|
|
std::string::size_type len = 0;
|
|
|
|
if (pos < s.length())
|
|
{
|
|
if (s[pos] == sigil)
|
|
{
|
|
// Skip leading '$' in the count!
|
|
++pos;
|
|
}
|
|
|
|
for
|
|
(
|
|
auto iter = s.cbegin() + pos;
|
|
iter != s.cend() && validVariableChar(*iter);
|
|
++iter
|
|
)
|
|
{
|
|
++len;
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
} // End namespace anonymous
|
|
|
|
|
|
namespace Foam
|
|
{
|
|
|
|
// Get dictionary or (optionally) environment variable
|
|
//
|
|
// Handles default and alternative values as per the POSIX shell.
|
|
// \code
|
|
// ${parameter:-defValue}
|
|
// ${parameter:+altValue}
|
|
// \endcode
|
|
static Foam::string getVariable
|
|
(
|
|
const word& name,
|
|
const dictionary* dictptr,
|
|
const bool allowEnv,
|
|
const bool allowEmpty,
|
|
const bool allowSubDict
|
|
)
|
|
{
|
|
// The type/position of the ":-" or ":+" alternative values
|
|
std::string::size_type altPos = 0;
|
|
|
|
// Check for parameter:-word or parameter:+word
|
|
const int altType =
|
|
findParameterAlternative(name, altPos, name.size()-1);
|
|
|
|
const word lookupName =
|
|
(altType ? word(name.substr(0,altPos), false) : name);
|
|
|
|
const entry* eptr =
|
|
(
|
|
(dictptr != nullptr)
|
|
? dictptr->findScoped(lookupName, keyType::LITERAL_RECURSIVE)
|
|
: nullptr
|
|
);
|
|
|
|
string value;
|
|
if (eptr)
|
|
{
|
|
value = entryToString(eptr, allowSubDict);
|
|
}
|
|
else if (allowEnv || dictptr == nullptr)
|
|
{
|
|
value = Foam::getEnv(lookupName);
|
|
}
|
|
|
|
if (value.empty() ? (altType == '-') : (altType == '+'))
|
|
{
|
|
// Not found or empty: use ":-" alternative value
|
|
// Found and not empty: use ":+" alternative value
|
|
value = name.substr(altPos + 2);
|
|
}
|
|
|
|
if (!allowEmpty && value.empty())
|
|
{
|
|
if (dictptr != nullptr)
|
|
{
|
|
auto& err =
|
|
FatalIOErrorInFunction(*dictptr)
|
|
<< "Cannot find dictionary entry ";
|
|
|
|
if (allowEnv)
|
|
{
|
|
err << "or environment ";
|
|
}
|
|
|
|
err << "variable '" << lookupName << "'" << nl
|
|
<< exit(FatalIOError);
|
|
}
|
|
else
|
|
{
|
|
FatalErrorInFunction
|
|
<< "Unknown variable '" << lookupName << "'" << nl
|
|
<< exit(FatalError);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
// Recursively expands (dictionary or environment) variable
|
|
// starting at index in string. Updates index.
|
|
//
|
|
// String: "abc ${var} def",
|
|
// Receive: "var} def"
|
|
//
|
|
// String: "abc ${{expr}} def"
|
|
// Receive: "{expr}} def"
|
|
//
|
|
// On return, the index will be adjust to be AFTER the closing '}'
|
|
static Foam::string recursiveExpand
|
|
(
|
|
const std::string& s,
|
|
std::string::size_type& index,
|
|
const dictionary* dictptr,
|
|
const bool allowEnv,
|
|
const bool allowEmpty,
|
|
const bool allowSubDict
|
|
)
|
|
{
|
|
///Info<< "process:" << index << "=" << s.substr(index) << endl;
|
|
|
|
// Track ${{ expr }} expressions
|
|
const bool isExpr = (index < s.size() && s[index] == '{');
|
|
|
|
if (isExpr)
|
|
{
|
|
++index;
|
|
}
|
|
|
|
// Initially called for a ${variable}, not ${{expr}}
|
|
bool isVar = !isExpr;
|
|
|
|
string out;
|
|
|
|
for (/*nil*/; index < s.size(); ++index)
|
|
{
|
|
///Info<< "remaining:" << index << "=" << s.substr(index) << endl;
|
|
if (s[index] == '$')
|
|
{
|
|
if (s[index+1] == '{')
|
|
{
|
|
// Recurse to parse variable name
|
|
index += 2;
|
|
|
|
string val =
|
|
recursiveExpand
|
|
(
|
|
s,
|
|
index,
|
|
dictptr,
|
|
allowEnv,
|
|
allowEmpty,
|
|
allowSubDict
|
|
);
|
|
|
|
out.append(val); // Append content
|
|
|
|
///Info<< "got:" << val << nl << "now:" << out << endl;
|
|
|
|
// Already skipped past '}' terminator?
|
|
if (s[index-1] == '}')
|
|
{
|
|
--index;
|
|
}
|
|
}
|
|
else if (validVariableChar(s[index+1]))
|
|
{
|
|
// A regular $var expansion without a surrounding {}.
|
|
|
|
const auto varLen = findVariableLen(s, index);
|
|
const word varName(s.substr(index+1, varLen), false);
|
|
index += varLen;
|
|
|
|
string val =
|
|
getVariable
|
|
(
|
|
varName,
|
|
dictptr,
|
|
allowEnv,
|
|
allowEmpty,
|
|
allowSubDict
|
|
);
|
|
|
|
out.append(val); // Append content
|
|
}
|
|
else
|
|
{
|
|
// Perhaps received something like '$[ ]' ? - pass through
|
|
out += s[index]; // Append char
|
|
}
|
|
}
|
|
else if (s[index] == '}')
|
|
{
|
|
// Closing an expression or variable
|
|
|
|
if (isExpr)
|
|
{
|
|
// Closes with '}}'
|
|
++index; // Index past closing '}'
|
|
|
|
if (s[index] == '}')
|
|
{
|
|
++index; // Index past closing '}'
|
|
}
|
|
else if (dictptr != nullptr)
|
|
{
|
|
// Missing '}'? - Warn/error/ignore
|
|
FatalIOErrorInFunction(*dictptr)
|
|
<< "Expansion ${{ is missing a closing '}}'\n"
|
|
<< exit(FatalIOError);
|
|
}
|
|
else
|
|
{
|
|
FatalErrorInFunction
|
|
<< "Expansion ${{ is missing a closing '}}'\n"
|
|
<< exit(FatalIOError);
|
|
}
|
|
|
|
///Info<< "eval <" << out << ">" << endl;
|
|
|
|
// Even with allow empty, expressions need content
|
|
const scalar sval = stringOps::toScalar(out);
|
|
const word val(Foam::name(sval));
|
|
|
|
return val;
|
|
}
|
|
else if (isVar)
|
|
{
|
|
// Variable - closes with '}'
|
|
|
|
++index; // Index past closing '}'
|
|
|
|
return
|
|
getVariable
|
|
(
|
|
out,
|
|
dictptr,
|
|
allowEnv,
|
|
allowEmpty,
|
|
allowSubDict
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Stray '}'? - Leave on output
|
|
|
|
out += s[index]; // append char
|
|
}
|
|
}
|
|
else
|
|
{
|
|
out += s[index]; // append char
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
|
|
static void expandString
|
|
(
|
|
std::string& s,
|
|
const dictionary* dictptr,
|
|
const bool allowEnv,
|
|
const bool allowEmpty,
|
|
const bool allowSubDict,
|
|
const char sigil
|
|
)
|
|
{
|
|
std::string::size_type varBeg = 0;
|
|
|
|
// Expand $VAR, ${VAR} or ${{EXPR}}
|
|
// Repeat until nothing more is found
|
|
while
|
|
(
|
|
(varBeg = s.find(sigil, varBeg)) != std::string::npos
|
|
&& varBeg < s.size()-1
|
|
)
|
|
{
|
|
if (varBeg == 0 || s[varBeg-1] != '\\')
|
|
{
|
|
if (s[varBeg+1] == '{')
|
|
{
|
|
// Recursive variable expansion mode: '${' or '${{'
|
|
const auto replaceBeg = varBeg;
|
|
|
|
varBeg += 2;
|
|
string varValue
|
|
(
|
|
recursiveExpand
|
|
(
|
|
s,
|
|
varBeg,
|
|
dictptr,
|
|
allowEnv,
|
|
allowEmpty,
|
|
allowSubDict
|
|
)
|
|
);
|
|
|
|
s.std::string::replace
|
|
(
|
|
replaceBeg,
|
|
varBeg - replaceBeg,
|
|
varValue
|
|
);
|
|
|
|
varBeg = replaceBeg+varValue.size();
|
|
}
|
|
else
|
|
{
|
|
const auto varLen(findVariableLen(s, varBeg, sigil));
|
|
const word varName(s.substr(varBeg+1, varLen), false);
|
|
|
|
string varValue
|
|
(
|
|
getVariable
|
|
(
|
|
varName,
|
|
dictptr,
|
|
allowEnv,
|
|
allowEmpty,
|
|
allowSubDict
|
|
)
|
|
);
|
|
|
|
s.std::string::replace
|
|
(
|
|
varBeg,
|
|
varName.size()+1,
|
|
varValue
|
|
);
|
|
varBeg += varValue.size();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++varBeg;
|
|
}
|
|
}
|
|
|
|
expandLeading(s);
|
|
}
|
|
|
|
} // End namespace Foam
|
|
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
|
|
|
|
std::string::size_type Foam::stringOps::count
|
|
(
|
|
const std::string& str,
|
|
const char c
|
|
)
|
|
{
|
|
std::string::size_type n = 0;
|
|
|
|
for (auto iter = str.cbegin(); iter != str.cend(); ++iter)
|
|
{
|
|
if (*iter == c)
|
|
{
|
|
++n;
|
|
}
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
std::string::size_type Foam::stringOps::count(const char* str, const char c)
|
|
{
|
|
if (!str)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
std::string::size_type n = 0;
|
|
|
|
for (const char *iter = str; *iter; ++iter)
|
|
{
|
|
if (*iter == c)
|
|
{
|
|
++n;
|
|
}
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
Foam::string Foam::stringOps::expand
|
|
(
|
|
const std::string& original,
|
|
const HashTable<string, word, string::hash>& mapping,
|
|
const char sigil
|
|
)
|
|
{
|
|
string s(original);
|
|
inplaceExpand(s, mapping);
|
|
return s;
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceExpand
|
|
(
|
|
std::string& s,
|
|
const HashTable<string, word, string::hash>& mapping,
|
|
const char sigil
|
|
)
|
|
{
|
|
std::string::size_type varBeg = 0;
|
|
|
|
// Expand $VAR or ${VAR}
|
|
// Repeat until nothing more is found
|
|
while
|
|
(
|
|
(varBeg = s.find(sigil, varBeg)) != std::string::npos
|
|
&& varBeg < s.size()-1
|
|
)
|
|
{
|
|
if (varBeg == 0 || s[varBeg-1] != '\\')
|
|
{
|
|
// Find end of first occurrence
|
|
std::string::size_type varEnd = varBeg;
|
|
std::string::size_type delim = 0;
|
|
|
|
// The type/position of the ":-" or ":+" alternative values
|
|
int altType = 0;
|
|
auto altPos = std::string::npos;
|
|
|
|
if (s[varBeg+1] == '{')
|
|
{
|
|
varEnd = s.find('}', varBeg);
|
|
delim = 1;
|
|
|
|
// Check for ${parameter:-word} or ${parameter:+word}
|
|
if (varEnd != std::string::npos)
|
|
{
|
|
altPos = varBeg;
|
|
altType = findParameterAlternative(s, altPos, varEnd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
varEnd += findVariableLen(s, varBeg, sigil);
|
|
}
|
|
|
|
if (varEnd == std::string::npos)
|
|
{
|
|
// Likely parsed '${...' without closing '}' - abort
|
|
break;
|
|
}
|
|
else if (varEnd == varBeg)
|
|
{
|
|
// Parsed '${}' or $badChar - skip over
|
|
varBeg = varEnd + 1;
|
|
}
|
|
else
|
|
{
|
|
const word varName
|
|
(
|
|
s.substr
|
|
(
|
|
varBeg + 1 + delim,
|
|
(
|
|
(altPos == std::string::npos ? varEnd : altPos)
|
|
- varBeg - 2*delim
|
|
)
|
|
),
|
|
false
|
|
);
|
|
|
|
std::string altValue;
|
|
if (altPos != std::string::npos)
|
|
{
|
|
// Had ":-" or ":+" alternative value
|
|
altValue = s.substr
|
|
(
|
|
altPos + 2,
|
|
varEnd - altPos - 2*delim
|
|
);
|
|
}
|
|
|
|
|
|
const auto fnd = mapping.cfind(varName);
|
|
|
|
if (fnd.found() ? (altType == '+') : (altType == '-'))
|
|
{
|
|
// Found and ":+" alternative
|
|
// Not-found and ":-" alternative
|
|
|
|
s.std::string::replace
|
|
(
|
|
varBeg,
|
|
varEnd - varBeg + 1,
|
|
altValue
|
|
);
|
|
varBeg += altValue.size();
|
|
}
|
|
else if (fnd.found())
|
|
{
|
|
// Found: use value
|
|
s.std::string::replace
|
|
(
|
|
varBeg,
|
|
varEnd - varBeg + 1,
|
|
*fnd
|
|
);
|
|
varBeg += (*fnd).size();
|
|
}
|
|
else
|
|
{
|
|
// Not-found: empty value
|
|
s.std::string::erase(varBeg, varEnd - varBeg + 1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++varBeg;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Foam::string Foam::stringOps::expand
|
|
(
|
|
const std::string& original,
|
|
const dictionary& dict,
|
|
const char sigil
|
|
)
|
|
{
|
|
string s(original);
|
|
inplaceExpand(s, dict, sigil);
|
|
return s;
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceExpand
|
|
(
|
|
std::string& s,
|
|
const dictionary& dict,
|
|
const bool allowEnv,
|
|
const bool allowEmpty,
|
|
const bool allowSubDict,
|
|
const char sigil
|
|
)
|
|
{
|
|
expandString(s, &dict, allowEnv, allowEmpty, allowSubDict, sigil);
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceExpand
|
|
(
|
|
std::string& s,
|
|
const dictionary& dict,
|
|
const char sigil
|
|
)
|
|
{
|
|
// Allow everything, including subDict expansions
|
|
// env=true, empty=true, subDict=true
|
|
expandString(s, &dict, true, true, true, sigil);
|
|
}
|
|
|
|
|
|
Foam::string Foam::stringOps::expand
|
|
(
|
|
const std::string& original,
|
|
const bool allowEmpty
|
|
)
|
|
{
|
|
string s(original);
|
|
inplaceExpand(s, allowEmpty);
|
|
return s;
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceExpand
|
|
(
|
|
std::string& s,
|
|
const bool allowEmpty
|
|
)
|
|
{
|
|
// Expand without a dictionary context
|
|
// allowEnv=true, allowSubDict=N/A
|
|
expandString(s, nullptr, true, allowEmpty, false, '$');
|
|
}
|
|
|
|
|
|
bool Foam::stringOps::inplaceReplaceVar(std::string& s, const word& varName)
|
|
{
|
|
if (s.empty() || varName.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const string content(Foam::getEnv(varName));
|
|
if (content.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto i = s.find(content);
|
|
if (i == std::string::npos)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
s.replace(i, content.size(), string("${" + varName + "}"));
|
|
return true;
|
|
}
|
|
|
|
|
|
Foam::string Foam::stringOps::trimLeft(const std::string& s)
|
|
{
|
|
if (!s.empty())
|
|
{
|
|
std::string::size_type beg = 0;
|
|
while (beg < s.size() && std::isspace(s[beg]))
|
|
{
|
|
++beg;
|
|
}
|
|
|
|
if (beg)
|
|
{
|
|
return s.substr(beg);
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceTrimLeft(std::string& s)
|
|
{
|
|
if (!s.empty())
|
|
{
|
|
std::string::size_type beg = 0;
|
|
while (beg < s.size() && std::isspace(s[beg]))
|
|
{
|
|
++beg;
|
|
}
|
|
|
|
if (beg)
|
|
{
|
|
s.erase(0, beg);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Foam::string Foam::stringOps::trimRight(const std::string& s)
|
|
{
|
|
if (!s.empty())
|
|
{
|
|
auto n = s.size();
|
|
while (n && std::isspace(s[n-1]))
|
|
{
|
|
--n;
|
|
}
|
|
|
|
if (n < s.size())
|
|
{
|
|
return s.substr(0, n);
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceTrimRight(std::string& s)
|
|
{
|
|
if (!s.empty())
|
|
{
|
|
auto n = s.size();
|
|
while (n && std::isspace(s[n-1]))
|
|
{
|
|
--n;
|
|
}
|
|
|
|
s.resize(n);
|
|
}
|
|
}
|
|
|
|
|
|
Foam::string Foam::stringOps::trim(const std::string& original)
|
|
{
|
|
string s(original);
|
|
inplaceTrim(s);
|
|
return s;
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceTrim(std::string& s)
|
|
{
|
|
inplaceTrimRight(s);
|
|
inplaceTrimLeft(s);
|
|
}
|
|
|
|
|
|
Foam::string Foam::stringOps::removeComments(const std::string& original)
|
|
{
|
|
string s(original);
|
|
inplaceRemoveComments(s);
|
|
return s;
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceRemoveComments(std::string& s)
|
|
{
|
|
const auto len = s.length();
|
|
|
|
if (len < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::string::size_type n = 0;
|
|
|
|
for (std::string::size_type i = 0; i < len; ++i)
|
|
{
|
|
char c = s[i];
|
|
|
|
if (n != i)
|
|
{
|
|
s[n] = c;
|
|
}
|
|
++n;
|
|
|
|
// The start of a C/C++ comment?
|
|
if (c == '/')
|
|
{
|
|
++i;
|
|
|
|
if (i == len)
|
|
{
|
|
// No further characters
|
|
break;
|
|
}
|
|
|
|
c = s[i];
|
|
|
|
if (c == '/')
|
|
{
|
|
// C++ comment - search for end-of-line
|
|
--n;
|
|
i = s.find('\n', ++i);
|
|
|
|
if (i == std::string::npos)
|
|
{
|
|
// Trucated - done
|
|
break;
|
|
}
|
|
}
|
|
else if (c == '*')
|
|
{
|
|
// C comment - search for '*/'
|
|
--n;
|
|
i = s.find("*/", ++i, 2);
|
|
|
|
if (i == std::string::npos)
|
|
{
|
|
// Trucated - done
|
|
break;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
else
|
|
{
|
|
// Not a C/C++ comment
|
|
if (n != i)
|
|
{
|
|
s[n] = c;
|
|
}
|
|
++n;
|
|
}
|
|
}
|
|
}
|
|
|
|
s.resize(n);
|
|
}
|
|
|
|
|
|
Foam::string Foam::stringOps::lower(const std::string& original)
|
|
{
|
|
string s(original);
|
|
inplaceLower(s);
|
|
return s;
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceLower(std::string& s)
|
|
{
|
|
for (auto iter = s.begin(); iter != s.end(); ++iter)
|
|
{
|
|
*iter = static_cast<std::string::value_type>
|
|
(
|
|
std::tolower(static_cast<unsigned char>(*iter))
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
Foam::string Foam::stringOps::upper(const std::string& original)
|
|
{
|
|
string s(original);
|
|
inplaceUpper(s);
|
|
return s;
|
|
}
|
|
|
|
|
|
void Foam::stringOps::inplaceUpper(std::string& s)
|
|
{
|
|
for (auto iter = s.begin(); iter != s.end(); ++iter)
|
|
{
|
|
*iter = static_cast<std::string::value_type>
|
|
(
|
|
std::toupper(static_cast<unsigned char>(*iter))
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
void Foam::stringOps::writeWrapped
|
|
(
|
|
OSstream& os,
|
|
const std::string& str,
|
|
const std::string::size_type width,
|
|
const std::string::size_type indent,
|
|
const bool escape
|
|
)
|
|
{
|
|
const auto len = str.length();
|
|
|
|
std::string::size_type pos = 0;
|
|
|
|
// Handle leading newlines
|
|
while (str[pos] == '\n' && pos < len)
|
|
{
|
|
os << '\n';
|
|
++pos;
|
|
}
|
|
|
|
while (pos < len)
|
|
{
|
|
// Potential end point and next point
|
|
std::string::size_type end = pos + width - 1;
|
|
std::string::size_type eol = str.find('\n', pos);
|
|
std::string::size_type next = string::npos;
|
|
|
|
if (end >= len)
|
|
{
|
|
// No more wrapping needed
|
|
end = len;
|
|
|
|
if (std::string::npos != eol && eol <= end)
|
|
{
|
|
// Manual '\n' break, next follows it (default behaviour)
|
|
end = eol;
|
|
}
|
|
}
|
|
else if (std::string::npos != eol && eol <= end)
|
|
{
|
|
// Manual '\n' break, next follows it (default behaviour)
|
|
end = eol;
|
|
}
|
|
else if (isspace(str[end]))
|
|
{
|
|
// Ended on a space - can use this directly
|
|
next = str.find_first_not_of(" \t\n", end); // Next non-space
|
|
}
|
|
else if (isspace(str[end+1]))
|
|
{
|
|
// The next one is a space - so we are okay
|
|
++end; // Otherwise the length is wrong
|
|
next = str.find_first_not_of(" \t\n", end); // Next non-space
|
|
}
|
|
else
|
|
{
|
|
// Line break will be mid-word
|
|
auto prev = str.find_last_of(" \t\n", end); // Prev word break
|
|
|
|
if (std::string::npos != prev && prev > pos)
|
|
{
|
|
end = prev;
|
|
next = prev + 1; // Continue from here
|
|
}
|
|
}
|
|
|
|
// The next position to continue from
|
|
if (std::string::npos == next)
|
|
{
|
|
next = end + 1;
|
|
}
|
|
|
|
// Has a length
|
|
if (end > pos)
|
|
{
|
|
// Indent following lines.
|
|
// The first one was already done prior to calling this routine.
|
|
if (pos)
|
|
{
|
|
for (std::string::size_type i = 0; i < indent; ++i)
|
|
{
|
|
os <<' ';
|
|
}
|
|
}
|
|
|
|
while (pos < end)
|
|
{
|
|
const char c = str[pos];
|
|
|
|
if (escape && c == '\\')
|
|
{
|
|
os << '\\';
|
|
}
|
|
os << c;
|
|
|
|
++pos;
|
|
}
|
|
os << nl;
|
|
}
|
|
|
|
pos = next;
|
|
}
|
|
}
|
|
|
|
|
|
// ************************************************************************* //
|