Skip to content
Snippets Groups Projects
stringOps.C 26.6 KiB
Newer Older
/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | Copyright (C) 2011-2016 OpenFOAM Foundation
     \\/     M anipulation  | Copyright (C) 2017 OpenCFD Ltd.
-------------------------------------------------------------------------------
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"
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //

namespace Foam
{

// Expand a leading <tag>/
// Convenient for frequently used directories
//
//   <etc>/        => user/group/other OpenFOAM directory
//   <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)
    auto delim = s.find(e);
        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);

    // Note that file is also allowed to be an empty string.

    if (tag == "etc")
    {
        s = findEtcFile(file);
    }
    else if (tag == "case")
    {
        s = fileName(getEnv("FOAM_CASE"))/file;
    }
    else if (tag == "constant" || tag == "system")
    {
        s = fileName(Foam::getEnv("FOAM_CASE"))/tag/file;
    }
}


// Expand a leading tilde
//   ~/        => home directory
//   ~OpenFOAM => user/group/other OpenFOAM directory
//   ~user     => home directory for specified user
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")
    {
        s = findEtcFile(file);
    }
    else
    {
        s = home(user)/file;
    }
}


// Expand leading contents:  "./", "~..", "<tag>/"
static void expandLeading(std::string& s)
{
    if (s.empty())
    {
        return;
    }
            // 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;
            expandLeadingTag(s, '<', '>');
            break;
        }
        case '~':
        {
            expandLeadingTilde(s);
            break;

} // end of namespace Foam
//! \cond fileScope
//  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;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

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 string& original,
    const HashTable<string, word, string::hash>& mapping,
    const char sigil
)
{
    inplaceExpand(s, mapping);
    return s;
void Foam::stringOps::inplaceExpand
    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] != '\\')
            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;
                varEnd = s.find('}', varBeg);
                // check for ${parameter:-word} or ${parameter:+word}
                if (varEnd != std::string::npos)
                    altPos = varBeg;
                    altType = findParameterAlternative(s, altPos, varEnd);
                string::const_iterator iter = s.cbegin() + varBeg + 1;
                // more generous in accepting keywords than for env variables
                    iter != s.cend()
                     || *iter == '.'
                     || *iter == ':'
                     || *iter == '_'
                    )
            if (varEnd == std::string::npos)
            {
                // likely parsed '${...' without closing '}' - abort
                break;
            }
            {
                // parsed '${}' or $badChar  - skip over
                const word varName
                    s.substr
                    (
                            (altPos == std::string::npos ? varEnd : altPos)
                          - varBeg - 2*delim
                std::string altValue;
                if (altPos != std::string::npos)
                    // had ":-" or ":+" alternative value
                    altValue = s.substr
                    (
                        altPos + 2,
                auto fnd = mapping.cfind(varName);
                    if (altPos != std::string::npos && altType == '+')
                    {
                        // was found, use ":+" alternative
                        s.std::string::replace
                        (
                    }
                    else
                    {
                        // was found, use value
                        s.std::string::replace
                        (
                else if (altPos != std::string::npos && altType == '-')
                    // was not found, use ":-" alternative
                    s.std::string::replace
                    (
                    // substitute with nothing, also for ":+" alternative
                    s.std::string::erase(varBeg, varEnd - varBeg + 1);
Foam::string Foam::stringOps::expand
(
    const string& original,
    const dictionary& dict,
    const char sigil
)
{
    string s(original);
    inplaceExpand(s, dict, sigil);
    return s;
Foam::string Foam::stringOps::getVariable
(
    const word& name,
    const dictionary& dict,
    const bool allowEnvVars,
    const bool allowEmpty
)
{
    string value;

    const entry* eptr = dict.lookupScopedEntryPtr(name, true, false);
    {
        OStringStream buf;
        // Force floating point numbers to be printed with at least
        // some decimal digits.
        buf << fixed;
        buf.precision(IOstream::defaultPrecision());

        // Fails for non-primitiveEntry
        dynamicCast<const primitiveEntry>(*eptr).write(buf, true);

        value = buf.str();
    }
    else if (allowEnvVars)
    {
        value = getEnv(name);

        if (value.empty() && !name.empty())
        {
            // 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);

            if (altType)
            {
                value = getEnv
                (
                    // var-name
                    word(name.substr(0, altPos), false)
                );

                // ":-" or ":+" alternative value
                if (value.empty() ? (altType == '-') : (altType == '+'))
                {
                    // alternative
                    value = name.substr(altPos + 2);
                }
            }
        }
    if (!allowEmpty && value.empty())
    {
        if (allowEnvVars)
            FatalIOErrorInFunction
            (
                dict
            )   << "Cannot find dictionary or environment variable "
                << name << exit(FatalIOError);
        }
        else
        {
            FatalIOErrorInFunction
            (
                dict
            )   << "Cannot find dictionary variable "
                << name << exit(FatalIOError);
        }
    }

    return value;
}


Foam::string Foam::stringOps::expand
(
    const string& s,
    std::string::size_type& index,
    const dictionary& dict,
    const bool allowEnvVars,
    const bool allowEmpty
)
{

    while (index < s.size())
    {
        if (s[index] == '$' && s[index+1] == '{')
        {
            // Recurse to parse variable name
            index += 2;
            string val = expand(s, index, dict, allowEnvVars, allowEmpty);
        }
        else if (s[index] == '}')
        {
            return getVariable(out, dict, allowEnvVars, allowEmpty);
            out.append(1, s[index]);  // append char
        ++index;
void Foam::stringOps::inplaceExpand
    const dictionary& dict,
    const bool allowEnvVars,
    const bool allowEmpty,
    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] != '\\')
            {
                // Recursive variable expansion mode
                auto stringStart = varBeg;
                varBeg += 2;
                string varValue
                (
                    expand
                    (
                        s,
                        dict,
                        allowEnvVars,
                        allowEmpty
                    )
                );

                s.std::string::replace
                (
                    stringStart,
                varBeg = stringStart+varValue.size();
                std::string::const_iterator iter = s.cbegin() + varBeg + 1;
                std::string::size_type varEnd = varBeg;

                // more generous in accepting keywords than for env variables
                while
                (
                    iter != s.cend()
                     || *iter == '.'
                     || *iter == ':'
                     || *iter == '_'
                    )
                )
                {
                    ++iter;
                }

                const word varName
                (
                    s.substr
                    (
                    ),
                    false
                );

                string varValue
                (
                    getVariable
                    (
                        varName,
                        dict,
                        allowEnvVars,
                        allowEmpty
                    )
                );

                s.std::string::replace
                (
                    varName.size()+1,
                    varValue
                );
void Foam::stringOps::inplaceExpand
    const dictionary& dict,
    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;
                varEnd = s.find('}', varBeg);
                delim = 1;
            }
            else
            {
                string::const_iterator iter = s.cbegin() + varBeg + 1;

                // more generous in accepting keywords than for env variables
                while
                (
                    iter != s.cend()
                     || *iter == '.'
                     || *iter == ':'
                     || *iter == '_'
                    )
                )
                {
                    ++iter;
            if (varEnd == std::string::npos)
            {
                // likely parsed '${...' without closing '}' - abort
                break;
            }
            {
                // parsed '${}' or $badChar  - skip over
            {
                const word varName
                (
                    s.substr
                    (
                        varBeg + 1 + delim,
                        varEnd - varBeg - 2*delim
                // Lookup in the dictionary without wildcards.
                // See note in primitiveEntry
                const entry* eptr = dict.lookupScopedEntryPtr

                // if defined - copy its entries
                if (eptr)
                {
                    OStringStream buf;
                    // Force floating point numbers to be printed with at least
                    // some decimal digits.
                    buf << fixed;
                    buf.precision(IOstream::defaultPrecision());
                    if (eptr->isDict())
                        eptr->dict().write(buf, false);
                        // Fail for non-primitiveEntry
                        dynamicCast<const primitiveEntry>
                        (
                        ).write(buf, true);
                    }

                    s.std::string::replace
                    (
                        buf.str()
                    );
                    varBeg += buf.str().size();
                    // not defined - leave original string untouched
Foam::string Foam::stringOps::expand
    const bool allowEmpty
    inplaceExpand(s, allowEmpty);
    return s;
void Foam::stringOps::inplaceExpand
    const bool allowEmpty
    std::string::size_type varBeg = 0;

    // Expand $VARS
    // Repeat until nothing more is found
    while
    (
        (varBeg = s.find('$', varBeg)) != std::string::npos
     && varBeg < s.size()-1
        if (varBeg == 0 || s[varBeg-1] != '\\')
            std::string::size_type varEnd = varBeg;
            std::string::size_type delim = 0;
            // The type/position of the ":-" or ":+" alternative values
            int altType = 0;
            std::string::size_type altPos = std::string::npos;
                varEnd = s.find('}', varBeg);
                // check for ${parameter:-word} or ${parameter:+word}
                if (varEnd != std::string::npos)
                    altPos = varBeg;
                    altType = findParameterAlternative(s, altPos, varEnd);
                string::const_iterator iter = s.cbegin() + varBeg + 1;
                    iter != s.cend()
                 && (std::isalnum(*iter) || *iter == '_')
            if (varEnd == std::string::npos)
            {
                // likely parsed '${...' without closing '}' - abort
                break;
            }
            {
                // parsed '${}' or $badChar  - skip over
                const word varName
                            (altPos == std::string::npos ? varEnd : altPos)
                          - varBeg - 2*delim
                std::string altValue;
                if (altPos != std::string::npos)
                    // had ":-" or ":+" alternative value
                    altValue = s.substr
                    (
                        altPos + 2,
                const string varValue = getEnv(varName);
                    if (altPos != std::string::npos && altType == '+')
                    {
                        // was found, use ":+" alternative
                        s.std::string::replace
                        (
                    }
                    else
                    {
                        // was found, use value
                        s.std::string::replace
                        (
                else if (altPos != std::string::npos)
                    // use ":-" or ":+" alternative values
                    if (altType == '-')
                    {
                        // was not found, use ":-" alternative
                        s.std::string::replace
                        (
                    }
                    else
                    {
                        // was not found, ":+" alternative implies
                        // substitute with nothing
                        s.std::string::erase(varBeg, varEnd - varBeg + 1);
                else if (allowEmpty)
                    s.std::string::erase(varBeg, varEnd - varBeg + 1);
                    FatalErrorInFunction
                        << "Unknown variable name '" << varName << "'"
bool Foam::stringOps::inplaceReplaceVar(std::string& s, const word& varName)
{
    if (s.empty() || varName.empty())
    {
        return false;
    }

    const string content(getEnv(varName));
    if (content.empty())
    {
        return false;
    }

    const std::string::size_type 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 string& s)
{
    if (!s.empty())
    {
        std::string::size_type beg = 0;
        while (beg < s.size() && std::isspace(s[beg]))
void Foam::stringOps::inplaceTrimLeft(std::string& s)
        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 string& s)
{
        auto n = s.size();
        while (n && std::isspace(s[n-1]))
void Foam::stringOps::inplaceTrimRight(std::string& s)
        auto n = s.size();
        while (n && std::isspace(s[n-1]))
}


Foam::string Foam::stringOps::trim(const string& original)
{
    return trimLeft(trimRight(original));
void Foam::stringOps::inplaceTrim(std::string& s)
    inplaceTrimRight(s);
    inplaceTrimLeft(s);
}


Foam::string Foam::stringOps::lower(const 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 string& original)
{
    string s(original);
    inplaceUpper(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))
        );
    }
}


// ************************************************************************* //