/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2019 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef OSGEARTH_STRING_UTILS_H
#define OSGEARTH_STRING_UTILS_H 1
#include <osgEarth/Common>
#include <osg/Vec3>
#include <osg/Vec3d>
#include <osg/Vec4>
#include <osg/Vec4ub>
#include <string>
#include <algorithm>
#include <vector>
#include <sstream>
#include <locale>
#include <iomanip>
#include <map>
#include <set>
#include <ctype.h>
namespace osgEarth
{
extern OSGEARTH_EXPORT const std::string EMPTY_STRING;
typedef std::vector<std::string> StringVector;
typedef std::set<std::string> StringSet;
/** Replaces all the instances of "pattern" with "replacement" in "in_out" */
extern OSGEARTH_EXPORT std::string& replaceIn(
std::string& in_out,
const std::string& pattern,
const std::string& replacement );
/** Replaces all the instances of "pattern" with "replacement" in "in_out" (case-insensitive) */
extern OSGEARTH_EXPORT std::string& ciReplaceIn(
std::string& in_out,
const std::string& pattern,
const std::string& replacement );
/**
* Trims whitespace from the ends of a string.
*/
extern OSGEARTH_EXPORT std::string trim( const std::string& in );
/**
* Trims whitespace from the ends of a string; in-place modification on the string to reduce string copies.
*/
extern OSGEARTH_EXPORT void trim2( std::string& str );
//! Removes leading and trailing whitespace, and replaces all other
//! whitespace with single spaces
extern OSGEARTH_EXPORT std::string trimAndCompress(const std::string& in);
/**
* True is "ref" starts with "pattern"
*/
extern OSGEARTH_EXPORT bool startsWith(
const std::string& ref,
const std::string& pattern,
bool caseSensitive =true,
const std::locale& locale =std::locale() );
/**
* True is "ref" ends with "pattern"
*/
extern OSGEARTH_EXPORT bool endsWith(
const std::string& ref,
const std::string& pattern,
bool caseSensitive =true,
const std::locale& locale =std::locale() );
/**
* Case-insensitive compare
*/
extern OSGEARTH_EXPORT bool ciEquals(
const std::string& lhs,
const std::string& rhs,
const std::locale& local = std::locale() );
/**
* Case-insensitive STL comparator
*/
struct OSGEARTH_EXPORT CIStringComp {
bool operator()(const std::string& lhs, const std::string& rhs) const;
};
extern OSGEARTH_EXPORT std::string joinStrings( const StringVector& input, char delim );
/** Returns a lower-case version of the input string. */
extern OSGEARTH_EXPORT std::string toLower( const std::string& input );
/** Parses a color string in the form "255 255 255 255" (r g b a [0..255]) into an OSG color. */
extern OSGEARTH_EXPORT osg::Vec4ub stringToColor(const std::string& str, osg::Vec4ub default_value);
/** Creates a string in the form "255 255 255 255" (r g b a [0..255]) from a color */
extern OSGEARTH_EXPORT std::string colorToString( const osg::Vec4ub& c );
/** Converts a string to a vec3f */
extern OSGEARTH_EXPORT osg::Vec3f stringToVec3f( const std::string& str, const osg::Vec3f& default_value );
/** Converts a vec3f to a string */
extern OSGEARTH_EXPORT std::string vec3fToString( const osg::Vec3f& v );
/** Parses an HTML color ("#rrggbb" or "#rrggbbaa") into an OSG color. */
extern OSGEARTH_EXPORT osg::Vec4f htmlColorToVec4f( const std::string& html );
/** Makes an HTML color ("#rrggbb" or "#rrggbbaa") from an OSG color. */
extern OSGEARTH_EXPORT std::string vec4fToHtmlColor( const osg::Vec4f& c );
/** Makes a valid filename out of a string */
extern OSGEARTH_EXPORT std::string toLegalFileName( const std::string& input, bool allowSubdir=false );
/** Generates a hashed integer for a string (poor man's MD5) */
extern OSGEARTH_EXPORT unsigned hashString( const std::string& input );
/** Same as hashString but returns a string value. */
extern OSGEARTH_EXPORT std::string hashToString(const std::string& input);
/**
* Gets the total number of seconds formatted as H:M:S
*/
extern OSGEARTH_EXPORT std::string prettyPrintTime( double seconds );
/**
* Gets a pretty printed version of the given size in MB.
*/
extern OSGEARTH_EXPORT std::string prettyPrintSize( double mb );
//------------------------------------------------------------------------
// conversion templates
// converts a string to primitive using serialization
template<typename T> inline T
as( const std::string& str, const T& default_value )
{
T temp = default_value;
std::istringstream strin( str );
if ( !strin.eof() ) strin >> temp;
return temp;
}
// template specialization for integers (to handle hex)
#define AS_INT_DEC_OR_HEX(TYPE) \
template<> inline TYPE \
as< TYPE >(const std::string& str, const TYPE & dv) { \
TYPE temp = dv; \
std::istringstream strin( trim(str) ); \
if ( !strin.eof() ) { \
if ( str.length() >= 2 && str[0] == '0' && str[1] == 'x' ) { \
strin.seekg( 2 ); \
strin >> std::hex >> temp; \
} \
else { \
strin >> temp; \
} \
} \
return temp; \
}
AS_INT_DEC_OR_HEX(int)
AS_INT_DEC_OR_HEX(unsigned)
AS_INT_DEC_OR_HEX(short)
AS_INT_DEC_OR_HEX(unsigned short)
AS_INT_DEC_OR_HEX(long)
AS_INT_DEC_OR_HEX(unsigned long)
// template specialization for a bool
template<> inline bool
as<bool>( const std::string& str, const bool& default_value )
{
std::string temp = toLower(str);
return
temp == "true" || temp == "yes" || temp == "on" ? true :
temp == "false" || temp == "no" || temp == "off" ? false :
default_value;
}
template<> inline osg::Vec3f
as<osg::Vec3f>( const std::string& str, const osg::Vec3f& default_value )
{
return stringToVec3f(str, default_value);
}
// template specialization for string
template<> inline std::string
as<std::string>( const std::string& str, const std::string& default_value )
{
return str;
}
// snips a substring and parses it.
template<typename T> inline bool
as(const std::string& in, unsigned start, unsigned len, T default_value)
{
std::string buf;
std::copy( in.begin()+start, in.begin()+start+len, std::back_inserter(buf) );
return as<T>(buf, default_value);
}
// converts a primitive to a string
// TODO: precision??
template<typename T> inline std::string
toString(const T& value)
{
std::stringstream out;
//out << std::setprecision(20) << std::fixed << value;
out << std::setprecision(20) << value;
std::string outStr;
outStr = out.str();
return outStr;
}
// template speciallization for a bool to print out "true" or "false"
template<> inline std::string
toString<bool>(const bool& value)
{
return value ? "true" : "false";
}
template<> inline std::string
toString<osg::Vec3f>(const osg::Vec3f& value)
{
return vec3fToString(value);
}
/**
* Assembles and returns an inline string using a stream-like << operator.
* Example:
* std::string str = Stringify() << "Hello, world " << variable;
*/
struct Stringify
{
operator std::string () const
{
std::string result;
result = buf.str();
return result;
}
template<typename T>
Stringify& operator << (const T& val) { buf << val; return (*this); }
Stringify& operator << (const Stringify& val) { buf << (std::string)val; return (*this); }
protected:
std::stringstream buf;
};
template<> inline
Stringify& Stringify::operator << <bool>(const bool& val) { buf << (val ? "true" : "false"); return (*this); }
template<> inline
Stringify& Stringify::operator << <osg::Vec3f>(const osg::Vec3f& val) {
buf << val.x() << " " << val.y() << " " << val.z(); return (*this); }
template<> inline
Stringify& Stringify::operator << <osg::Vec3d>(const osg::Vec3d& val ) {
buf << val.x() << " " << val.y() << " " << val.z(); return (*this); }
template<> inline
Stringify& Stringify::operator << <osg::Vec4f>(const osg::Vec4f& val) {
buf << val.r() << " " << val.g() << " " << val.b() << " " << val.a(); return (*this); }
/**
* Splits a string up into a vector of strings based on a set of
* delimiters, quotes, and rules.
*/
class OSGEARTH_EXPORT StringTokenizer
{
public:
StringTokenizer( const std::string& delims =" \t\r\n", const std::string& quotes ="'\"" );
StringTokenizer(
const std::string& input, StringVector& output,
const std::string& delims =" \t\r\n", const std::string& quotes ="'\"",
bool keepEmpties =true, bool trimTokens =true);
void tokenize( const std::string& input, StringVector& output ) const;
bool& keepEmpties() { return _allowEmpties; }
bool& trimTokens() { return _trimTokens; }
void addDelim( char delim, bool keepAsToken =false );
void addDelims( const std::string& delims, bool keepAsTokens =false );
void addQuote( char delim, bool keepInToken =false );
void addQuotes( const std::string& delims, bool keepInTokens =false );
private:
typedef std::map<char,bool> TokenMap;
TokenMap _delims;
TokenMap _quotes;
bool _allowEmpties;
bool _trimTokens;
};
}
#endif // OSGEARTH_STRING_UTILS_H
/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2019 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include <osgEarth/StringUtils>
#include <cctype>
#include <cstring>
using namespace osgEarth;
StringTokenizer::StringTokenizer( const std::string& delims, const std::string& quotes ) :
_allowEmpties( true ),
_trimTokens ( true )
{
addDelims( delims );
addQuotes( quotes );
}
StringTokenizer::StringTokenizer(const std::string& input,
StringVector& output,
const std::string& delims,
const std::string& quotes,
bool allowEmpties,
bool trimTokens ) :
_allowEmpties( allowEmpties ),
_trimTokens ( trimTokens )
{
addDelims( delims );
addQuotes( quotes );
tokenize( input, output );
}
void
StringTokenizer::addDelim( char delim, bool keep )
{
_delims[delim] = keep;
}
void
StringTokenizer::addDelims( const std::string& delims, bool keep )
{
for( unsigned i=0; i<delims.size(); ++i )
addDelim( delims[i], keep );
}
void
StringTokenizer::addQuote( char quote, bool keep )
{
_quotes[quote] = keep;
}
void
StringTokenizer::addQuotes( const std::string& quotes, bool keep )
{
for( unsigned i=0; i<quotes.size(); ++i )
addQuote( quotes[i], keep );
}
void
StringTokenizer::tokenize( const std::string& input, StringVector& output ) const
{
output.clear();
std::stringstream buf;
bool quoted = false;
char lastQuoteChar = '\0';
for( std::string::const_iterator i = input.begin(); i != input.end(); ++i )
{
char c = *i;
TokenMap::const_iterator q = _quotes.find( c );
if ( quoted )
{
if( q != _quotes.end() && lastQuoteChar == c)
{
quoted = false;
lastQuoteChar = '\0';
if ( q->second )
buf << c;
}
else
{
buf << c;
}
}
else
{
if ( q != _quotes.end() )
{
quoted = true;
lastQuoteChar = c;
if ( q->second )
buf << c;
}
else
{
TokenMap::const_iterator d = _delims.find( c );
if ( d == _delims.end() )
{
buf << c;
}
else
{
std::string token = buf.str();
if ( _trimTokens )
trim2( token );
if ( _allowEmpties || !token.empty() )
output.push_back( token );
if ( d->second == true )
{
output.push_back( std::string(1, c) );
}
buf.str("");
}
}
}
}
std::string bufstr = buf.str();
if ( _trimTokens )
trim2( bufstr );
if ( !bufstr.empty() )
output.push_back( bufstr );
}
//--------------------------------------------------------------------------
const std::string osgEarth::EMPTY_STRING;
std::string
osgEarth::toLegalFileName(const std::string& input, bool allowSubdirs)
{
// See: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282
// We omit '-' so we can use it for the HEX identifier.
static const std::string legalWithoutSubdirs("ABCDEFGHIJKLMNOPQRSTUVQXYZabcdefghijklmnopqrstuvwxyz0123456789_.");
static const std::string legalWithDirs ("ABCDEFGHIJKLMNOPQRSTUVQXYZabcdefghijklmnopqrstuvwxyz0123456789_./");
std::size_t pos = input.find("://");
pos = pos == std::string::npos ? 0 : pos+3;
const std::string& legal = allowSubdirs? legalWithDirs : legalWithoutSubdirs;
std::stringstream buf;
for( ; pos < input.size(); ++pos )
{
std::string::const_reference c = input[pos];
if (legal.find(c) != std::string::npos)
buf << c;
else
buf << "-" << std::hex << static_cast<unsigned>(c) << "-";
}
std::string result;
result = buf.str();
return result;
}
/** MurmurHash 2.0 (http://sites.google.com/site/murmurhash/) */
unsigned
osgEarth::hashString( const std::string& input )
{
const unsigned int m = 0x5bd1e995;
const int r = 24;
unsigned int len = input.length();
const char* data = input.c_str();
unsigned int h = m ^ len; // using "m" as the seed.
while(len >= 4)
{
unsigned int k = *(unsigned int *)data;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
switch(len)
{
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0];
h *= m;
};
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}
std::string
osgEarth::hashToString(const std::string& input)
{
return Stringify() << std::hex << std::setw(8) << std::setfill('0') << hashString(input);
}
/** Parses an HTML color ("#rrggbb" or "#rrggbbaa") into an OSG color. */
osg::Vec4f
osgEarth::htmlColorToVec4f( const std::string& html )
{
std::string t = osgEarth::toLower(html);
osg::Vec4ub c(0,0,0,255);
if ( t.length() >= 7 ) {
c.r() |= t[1]<='9' ? (t[1]-'0')<<4 : (10+(t[1]-'a'))<<4;
c.r() |= t[2]<='9' ? (t[2]-'0') : (10+(t[2]-'a'));
c.g() |= t[3]<='9' ? (t[3]-'0')<<4 : (10+(t[3]-'a'))<<4;
c.g() |= t[4]<='9' ? (t[4]-'0') : (10+(t[4]-'a'));
c.b() |= t[5]<='9' ? (t[5]-'0')<<4 : (10+(t[5]-'a'))<<4;
c.b() |= t[6]<='9' ? (t[6]-'0') : (10+(t[6]-'a'));
if ( t.length() == 9 ) {
c.a() = 0;
c.a() |= t[7]<='9' ? (t[7]-'0')<<4 : (10+(t[7]-'a'))<<4;
c.a() |= t[8]<='9' ? (t[8]-'0') : (10+(t[8]-'a'));
}
}
return osg::Vec4f( ((float)c.r())/255.0f, ((float)c.g())/255.0f, ((float)c.b())/255.0f, ((float)c.a())/255.0f );
}
/** Makes an HTML color ("#rrggbb" or "#rrggbbaa") from an OSG color. */
std::string
osgEarth::vec4fToHtmlColor( const osg::Vec4f& c )
{
std::stringstream buf;
buf << "#";
buf << std::hex << std::setw(2) << std::setfill('0') << (int)(c.r()*255.0f);
buf << std::hex << std::setw(2) << std::setfill('0') << (int)(c.g()*255.0f);
buf << std::hex << std::setw(2) << std::setfill('0') << (int)(c.b()*255.0f);
if ( c.a() < 1.0f )
buf << std::hex << std::setw(2) << std::setfill('0') << (int)(c.a()*255.0f);
std::string ssStr;
ssStr = buf.str();
return ssStr;
}
/** Parses a color string in the form "255 255 255 255" (r g b a [0..255]) into an OSG color. */
osg::Vec4ub
osgEarth::stringToColor(const std::string& str, osg::Vec4ub default_value)
{
osg::Vec4ub color = default_value;
std::istringstream strin(str);
int r, g, b, a;
if (strin >> r && strin >> g && strin >> b && strin >> a)
{
color.r() = (unsigned char)r;
color.g() = (unsigned char)g;
color.b() = (unsigned char)b;
color.a() = (unsigned char)a;
}
return color;
}
/** Creates a string in the form "255 255 255 255" (r g b a [0..255]) from a color */
std::string
osgEarth::colorToString( const osg::Vec4ub& c )
{
std::stringstream ss;
ss << (int)c.r() << " " << (int)c.g() << " " << (int)c.b() << " " << (int)c.a();
std::string ssStr;
ssStr = ss.str();
return ssStr;
}
/** Converts a string to a vec3f */
osg::Vec3f
osgEarth::stringToVec3f( const std::string& str, const osg::Vec3f& default_value )
{
std::stringstream buf(str);
osg::Vec3f out = default_value;
buf >> out.x();
if ( !buf.eof() ) {
buf >> out.y() >> out.z();
}
else {
out.y() = out.x();
out.z() = out.x();
}
return out;
}
/** Converts a vec3f to a string */
std::string
osgEarth::vec3fToString( const osg::Vec3f& v )
{
std::stringstream buf;
buf << std::setprecision(6)
<< v.x() << " " << v.y() << " " << v.z()
<< std::endl;
std::string result;
result = buf.str();
return result;
}
/** Replaces all the instances of "sub" with "other" in "s". */
std::string&
osgEarth::replaceIn( std::string& s, const std::string& sub, const std::string& other)
{
if ( sub.empty() ) return s;
size_t b=0;
for( ; ; )
{
b = s.find( sub, b );
if ( b == s.npos ) break;
s.replace( b, sub.size(), other );
b += other.size();
}
return s;
}
std::string&
osgEarth::ciReplaceIn( std::string& s, const std::string& pattern, const std::string& replacement )
{
if ( pattern.empty() ) return s;
std::string upperSource = s;
std::transform( upperSource.begin(), upperSource.end(), upperSource.begin(), (int(*)(int))std::toupper );
std::string upperPattern = pattern;
std::transform( upperPattern.begin(), upperPattern.end(), upperPattern.begin(), (int(*)(int))std::toupper );
for( size_t b = 0; ; )
{
b = upperSource.find( upperPattern, b );
if ( b == s.npos ) break;
s.replace( b, pattern.size(), replacement );
upperSource.replace( b, upperPattern.size(), replacement );
b += replacement.size();
}
return s;
}
/**
* Trims whitespace from the ends of a string.
* by Rodrigo C F Dias
* http://www.codeproject.com/KB/stl/stdstringtrim.aspx
*/
void
osgEarth::trim2( std::string& str )
{
static const std::string whitespace (" \t\f\v\n\r");
std::string::size_type pos = str.find_last_not_of( whitespace );
if(pos != std::string::npos) {
str.erase(pos + 1);
pos = str.find_first_not_of( whitespace );
if(pos != std::string::npos) str.erase(0, pos);
}
else str.erase(str.begin(), str.end());
}
/**
* Trims whitespace from the ends of a string, returning a
* copy of the string with whitespace removed.
*/
std::string
osgEarth::trim( const std::string& in )
{
std::string str = in;
trim2( str );
return str;
}
std::string
osgEarth::trimAndCompress(const std::string& in)
{
bool inwhite = true;
std::stringstream buf;
for (unsigned i = 0; i < in.length(); ++i)
{
char c = in[i];
if (::isspace(c))
{
if (!inwhite)
{
buf << ' ';
inwhite = true;
}
}
else
{
inwhite = false;
buf << c;
}
}
std::string r;
r = buf.str();
trim2(r);
return r;
}
std::string
osgEarth::joinStrings( const StringVector& input, char delim )
{
std::stringstream buf;
for( StringVector::const_iterator i = input.begin(); i != input.end(); ++i )
{
buf << *i;
if ( (i+1) != input.end() ) buf << delim;
}
std::string result;
result = buf.str();
return result;
}
/** Returns a lower-case version of the input string. */
std::string
osgEarth::toLower( const std::string& input )
{
std::string output = input;
std::transform( output.begin(), output.end(), output.begin(), ::tolower );
return output;
}
std::string
osgEarth::prettyPrintTime( double seconds )
{
int hours = (int)floor(seconds / (3600.0) );
seconds -= hours * 3600.0;
int minutes = (int)floor(seconds/60.0);
seconds -= minutes * 60.0;
std::stringstream buf;
buf << hours << ":" << minutes << ":" << seconds;
return buf.str();
}
std::string
osgEarth::prettyPrintSize( double mb )
{
std::stringstream buf;
// Convert to terabytes
if ( mb > 1024 * 1024 )
{
buf << (mb / (1024.0*1024.0)) << " TB";
}
else if (mb > 1024)
{
buf << (mb / 1024.0) << " GB";
}
else
{
buf << mb << " MB";
}
return buf.str();
}
namespace
{
template<typename charT>
struct ci_equal {
ci_equal( const std::locale& loc ) : _loc(loc) { }
bool operator()(charT c1, charT c2) {
return std::toupper(c1,_loc) == std::toupper(c2,_loc);
}
const std::locale& _loc;
};
}
bool
osgEarth::ciEquals(const std::string& lhs, const std::string& rhs, const std::locale& loc )
{
if ( lhs.length() != rhs.length() )
return false;
for( unsigned i=0; i<lhs.length(); ++i )
{
if ( std::toupper(lhs[i], loc) != std::toupper(rhs[i], loc) )
return false;
}
return true;
}
#if defined(WIN32) && !defined(__CYGWIN__)
# define STRICMP ::stricmp
#else
# define STRICMP ::strcasecmp
#endif
bool CIStringComp::operator()(const std::string& lhs, const std::string& rhs) const
{
return STRICMP( lhs.c_str(), rhs.c_str() ) < 0;
}
bool
osgEarth::startsWith( const std::string& ref, const std::string& pattern, bool caseSensitive, const std::locale& loc )
{
if ( pattern.length() > ref.length() )
return false;
if ( caseSensitive )
{
for( unsigned i=0; i<pattern.length(); ++i )
{
if ( ref[i] != pattern[i] )
return false;
}
}
else
{
for( unsigned i=0; i<pattern.length(); ++i )
{
if ( std::toupper(ref[i], loc) != std::toupper(pattern[i],loc) )
return false;
}
}
return true;
}
bool
osgEarth::endsWith( const std::string& ref, const std::string& pattern, bool caseSensitive, const std::locale& loc )
{
if ( pattern.length() > ref.length() )
return false;
unsigned offset = ref.size()-pattern.length();
if ( caseSensitive )
{
for( unsigned i=0; i < pattern.length(); ++i )
{
if ( ref[i+offset] != pattern[i] )
return false;
}
}
else
{
for( unsigned i=0; i < pattern.length(); ++i )
{
if ( std::toupper(ref[i+offset], loc) != std::toupper(pattern[i],loc) )
return false;
}
}
return true;
}