Related
I know, I know - that question title is very much all over the place. However, I am not sure what could be an issue here that is causing what I am witnessing.
I have the following method in class Project that is being unit tested:
bool Project::DetermineID(std::string configFile, std::string& ID)
{
std::ifstream config;
config.open(configFile);
if (!config.is_open()) {
WARNING << "Failed to open the configuration file for processing ID at: " << configFile;
return false;
}
std::string line = "";
ID = "";
bool isConfigurationSection = false;
bool isConfiguration = false;
std::string tempID = "";
while (std::getline(config, line))
{
std::transform(line.begin(), line.end(), line.begin(), ::toupper); // transform the line to all capital letters
boost::trim(line);
if ((line.find("IDENTIFICATIONS") != std::string::npos) && (!isConfigurationSection)) {
// remove the "IDENTIFICATIONS" part from the current line we're working with
std::size_t idStartPos = line.find("IDENTIFICATIONS");
line = line.substr(idStartPos + strlen("IDENTIFICATIONS"), line.length() - idStartPos - strlen("IDENTIFICATIONS"));
boost::trim(line);
isConfigurationSection = true;
}
if ((line.find('{') != std::string::npos) && isConfigurationSection) {
std::size_t bracketPos = line.find('{');
// we are working within the ids configuration section
// determine if this is the first character of the line, or if there is an ID that precedes the {
if (bracketPos == 0) {
// is the first char
// remove the bracket and keep processing
line = line.substr(1, line.length() - 1);
boost::trim(line);
}
else {
// the text before { is a temp ID
tempID = line.substr(0, bracketPos - 1);
isConfiguration = true;
line = line.substr(bracketPos, line.length() - bracketPos);
boost::trim(line);
}
}
if ((line.find("PORT") != std::string::npos) && isConfiguration) {
std::size_t indexOfEqualSign = line.find('=');
if (indexOfEqualSign == std::string::npos) {
WARNING << "Unable to determine the port # assigned to " << tempID;
}
else {
std::string portString = "";
portString = line.substr(indexOfEqualSign + 1, line.length() - indexOfEqualSign - 1);
boost::trim(portString);
// confirm that the obtained port string is not an empty value
if (portString.empty()) {
WARNING << "Failed to obtain the \"Port\" value that is set to " << tempID;
}
else {
// attempt to convert the string to int
int workingPortNum = 0;
try {
workingPortNum = std::stoi(portString);
}
catch (...) {
WARNING << "Failed to convert the obtained \"Port\" value that is set to " << tempID;
}
if (workingPortNum != 0) {
// check if this port # is the same port # we are publishing data on
if (workingPortNum == this->port) {
ID = tempID;
break;
}
}
}
}
}
}
config.close();
if (ID.empty())
return false;
else
return true;
}
The goal of this method is to parse any text file for the ID portion, based on matching the port # that the application is publishing data to.
Format of the file is like this:
Idenntifications {
ID {
port = 1001
}
}
In a separate Visual Studio project that unit tests various methods, including this Project::DetermineID method.
#define STRINGIFY(x) #x
#define EXPAND(x) STRINGIFY(x)
TEST_CLASS(ProjectUnitTests) {
Project* parser;
std::string projectDirectory;
TEST_METHOD_INITIALIZE(ProjectUnitTestInitialization) {
projectDirectory = EXPAND(UNITTESTPRJ);
projectDirectory.erase(0, 1);
projectDirectory.erase(projectDirectory.size() - 2);
parser = Project::getClass(); // singleton method getter/initializer
}
// Other test methods are present and pass/fail accordingly
TEST_METHOD(DetermineID) {
std::string ID = "";
bool x = parser ->DetermineAdapterID(projectDirectory + "normal.cfg", ID);
Assert::IsTrue(x);
}
};
Now, when I run the tests, DetermineID fails and the stack trace states:
DetermineID
Source: Project Tests.cpp line 86
Duration: 2 sec
Message:
Assert failed
Stack Trace:
ProjectUnitTests::DetermineID() line 91
Now, in my test .cpp file, TEST_METHOD(DetermineID) { is present on line 86. But that method's } is located on line 91, as the stack trace indicates.
And, when debugging, the unit test passes, because the return of x in the TEST_METHOD is true.
Only when running the test individually or running all tests does that test method fail.
Some notes that may be relevant:
This is a single-threaded application with no tasks scheduled (no race condition to worry about supposedly)
There is another method in the Project class that also processes a file with an std::ifstream same as this method does
That method has its own test method that has been written and passes without any problems
The test method also access the "normal.cfg" file
Yes, this->port has an assigned value
Thus, my questions are:
Why does the stack trace reference the closing bracket for the test method instead of the single Assert within the method that is supposedly failing?
How to get the unit test to pass when it is ran? (Since it currently only plasses during debugging where I can confirm that x is true).
If the issue is a race condition where perhaps the other test method is accessing the "normal.cfg" file, why does the test method fail even when the method is individually ran?
Any support/assistance here is very much appreciated. Thank you!
Please help me fulfill my dreams of turning this sequence into a meaningful output. :)
See regex in action, it works!: http://regex101.com/r/iM4yN2/1
Now all I need is to know how to use it. If I could put this into a multidimensional array e.g. configFile[0][0] = [Tuner,] that would work. Or if I could turn this into a comma separated list, I could then parse that again and put it into arrays and finally out to individual variables. Anyway, you don't need to spell out how to actually assign the variables, I'll create another question if I really need help with that. Mainly I need help with the use of regex functions and outputting data into SOME variable where I can access the various text on either side of the = sign per line.
regex:
^[\t ]*(.*?)\s*=[\t ]*(.*?)(#.*)?$
test string:
### MODULES ###
Tuner =
PitchDetector = 0
PhaseLocker = 0
FileOutput = 1
### FILE MANAGER ###
RenameFile_AvgFreq = dfgsdfg dsf gdfs g #gdrgk
RenameFile_NoteName = 0
RenameFile_Prefix = "The String Is Good"
RenameFile_Suffix = ""
OutputFolder = "..\Folder\String\"
### PITCH DETECTOR ###
AnalysisChannel = 1 #int starting from 1
BlockSize = 8 #power of 2
Overlap = 16 #power of 2
NormalizeForDetection = 0
### TUNER ###
Smoothing = 0.68
Envelope = 0.45
### PHASELOCKER ###
FFTSize = 1024 #powert of 2
FFTOverlap = 54687
WindowType = 0
MaxFreq = 5000
my variables:
//Modules
bool Tuner;
bool PitchDetector;
bool PhaseLocker;
bool FileOutput;
//File Manager
bool RenameFile_AvgFreq;
bool RenameFile_NoteName;
std::string RenameFile_Prefix;
std::string RenameFile_Suffix;
std::string OutputFolder;
//Pitch Detector
int AnalysisChannel;
int BlockSize;
int Overlap;
bool NormalizeForDetection;
//Tuner
float Smoothing;
float Envelope;
//Phaselocker
int FFTSize;
int FFTOverlap;
int FFTWindowType;
float FFTMaxFreq;
final notes: i spent a long time looking at c++ regex functions... very confusing stuff. I know how to do this in python without thinking twice.
Include the following:
#include <string>
#include <regex>
Declare a string and regex type:
std::string s;
std::regex e;
In your main function, assign string and regex variables and call regex function (you could assign the variables when you declare them as well):
int main()
{
s="i will only 349 output 853 the numbers 666"
e="(\\d+)"
s = std::regex_replace(s, e, "$1\n", std::regex_constants::format_no_copy);
return 0;
}
Notice how I am putting the results right back into the string (s). Of course, you could use a different string to store the result. The "std::regex_constants::format_no_copy" is a flag that tells the regex function to output only "substrings" aka group matches. Also notice how I am using double slash on the "\d+". Try double slashes if your regex pattern isn't working.
To find key/value pairs with regex, e.g. "BlockSize = 1024", you could create a pattern such as:
BlockSize\s*=\s*((?:[\d.]+)|(?:".*"))
in c++ you could create that regex pattern with:
expr = key+"\\s*=\\s*((?:[\\d.]+)|(?:\".*\"))";
and return the match with:
config = std::regex_replace(config, expr, "$1", std::regex_constants::format_no_copy);
and put it all together in a function with the ability to return a default value:
std::string Config_GetValue(std::string key, std::string config, std::string defval)
{
std::regex expr;
match = key+"\\s*=\\s*((?:[\\d.]+)|(?:\".*\"))";
config = std::regex_replace(config, expr, "$1", std::regex_constants::format_no_copy);
return config == "" ? defval : config;
}
FULL CODE (using std::stoi and std::stof to convert string to number when needed, and using auto type because right-hand side (RHS) makes it clear what the type is):
#include "stdafx.h"
#include <string>
#include <regex>
#include <iostream>
std::string Config_GetValue(std::string key, std::string config, std::string defval)
{
std::regex expr;
match = key+"\\s*=\\s*((?:[\\d.]+)|(?:\".*\"))";
config = std::regex_replace(config, expr, "$1", std::regex_constants::format_no_copy);
return config == "" ? defval : config;
}
int main()
{
//test string
std::string s = " ### MODULES ###\nTuner = \n PitchDetector = 1\n PhaseLocker = 0 \nFileOutput = 1\n\n### FILE MANAGER ###\nRenameFile_AvgFreq = dfgsdfg dsf gdfs g #gdrgk\nRenameFile_NoteName = 0\n RenameFile_Prefix = \"The String Is Good\"\nRenameFile_Suffix = \"\"\nOutputFolder = \"..\\Folder\\String\\\"\n\n### PITCH DETECTOR ###\nAnalysisChannel = 1 #int starting from 1\nBlockSize = 1024 #power of 2\nOverlap = 16 #power of 2\nNormalizeForDetection = 0\n\n### TUNER ###\nSmoothing = 0.68\nEnvelope = 0.45\n\n### PHASELOCKER ###\nFFTSize = 1024 #powert of 2\nFFTOverlap = 54687\nWindowType = 0\nMaxFreq = 5000";
//Modules
auto FileOutput = stoi(Config_GetValue("FileOutput", s, "0"));
auto PitchDetector = stoi(Config_GetValue("PitchDetector", s, "0"));
auto Tuner = stoi(Config_GetValue("Tuner", s, "0"));
auto PhaseLocker = stoi(Config_GetValue("PhaseLocker", s, "0"));
//File Manager
auto RenameFile_AvgFreq = stoi(Config_GetValue("RenameFile_AvgFreq", s, "0"));
auto RenameFile_NoteName = stoi(Config_GetValue("RenameFile_NoteName", s, "0"));
auto RenameFile_Prefix = Config_GetValue("RenameFile_Prefix", s, "");
auto RenameFile_Suffix = Config_GetValue("RenameFile_Suffix", s, "");
auto OutputFolder = Config_GetValue("FileOutput", s, "");
//Pitch Detector
auto AnalysisChannel = stoi(Config_GetValue("AnalysisChannel", s, "1"));
auto BlockSize = stoi(Config_GetValue("BlockSize", s, "4096"));
auto Overlap = stoi(Config_GetValue("Overlap", s, "8"));
auto NormalizeForDetection = stoi(Config_GetValue("NormalizeForDetection", s, "0"));
//Tuner
auto Smoothing = stof(Config_GetValue("Smoothing", s, ".5"));
auto Envelope = stof(Config_GetValue("Envelope", s, ".3"));
auto TransientTime = stof(Config_GetValue("TransientTime", s, "0"));
//Phaselocker
auto FFTSize = stoi(Config_GetValue("FFTSize", s, "1"));
auto FFTOverlap = stoi(Config_GetValue("FFTOverlap", s, "1"));
auto FFTWindowType = stoi(Config_GetValue("FFTWindowType", s, "1"));
auto FFTMaxFreq = stof(Config_GetValue("FFTMaxFreq", s, "0.0"));
std::cout << "complete";
return 0;
}
Another way of doing this is with regex_iterator:
#include <regex>
using std::regex;
using std::sregex_iterator;
void CreateConfig(string config)
{
//group 1,2,3,4,5 = key,float,int,string,bool
regex expr("^[\\t ]*(\\w+)[\\t ]*=[\\t ]*(?:(\\d+\\.+\\d+|\\.\\d+|\\d+\\.)|(\\d+)|(\"[^\\r\\n:]*\")|(TRUE|FALSE))[^\\r\\n]*$", std::regex_constants::icase);
for (sregex_iterator it(config.begin(), config.end(), expr), itEnd; it != itEnd; ++it)
{
if ((*it)[2] != "") cout << "FLOAT -> " << (*it)[1] << " = " <<(*it)[2] << endl;
else if ((*it)[3] != "") cout << "INT -> " << (*it)[1] << " = " <<(*it)[3] << endl;
else if ((*it)[4] != "") cout << "STRING -> " << (*it)[1] << " = " <<(*it)[4] << endl;
else if ((*it)[5] != "") cout << "BOOL -> " << (*it)[1] << " = " << (*it)[5] << endl;
}
}
int main()
{
string s = "what = 1\n: MODULES\nFileOutput = \"on\" :bool\nPitchDetector = TRuE :bool\nTuner = on:bool\nHarmSplitter = off:bool\nPhaseLocker = on\n\nyes\n junk output = \"yes\"\n\n: FILE MANAGER\nRenameFile AvgFreq = 1 \nRenameFile_NoteName = 0 :bool\nRenameFile_Prefix = \"The Strin:g Is Good\" :string\nRenameFile_Suffix = \"\":string\nOutputFolder = \"..\\Folder\\String\\\" :relative path\n\n: PITCH DETECTOR\nAnalysisChannel = 1 :integer starting from 1\nBlockSize = 8 :power of 2\nOverlap = 16 :power of 2\nNormalizeForDetection = 0 :bool\n\n: TUNER\nSmoothing = 0.68 :float\nEnvelope = 0.45 :float\n\n: PHASE LOCKER\nFFTSize = 1024 :power of 2\nFFTOverlap = 54687 :power of 2\nWindowType = 0 :always set to 0\nMaxFreq = 5000 :float";
CreateConfig(s);
return 0;
}
Let's break this down. The regex expression I created uses a ^regexy stuff goes here$ format so that each line of text is considered individually: ^=start of line, $=end of line. The regex looks for: variable_name = decimal OR number OR string OR (true OR false). Because each type is stored in its own group, we know what type every match is going to be.
To explain the for loop, I will write the code a few different ways
//You can declare more than one variable of the same type:
for (sregex_iterator var1(str.begin(), str.end(), regexExpr), var2); var1 != var2; var1++)
//Or you can delcare it outside the for loop:
sregex_iterator var1(str.begin(), str.end(), regexExpr);
sregex_iterator var2;
for (; var1 != var2; var1++)
//Or the more classic way:
sregex_iterator var1(str.begin(), str.end(), regexExpr);
for (sregex_iterator var2; var1 != var2; var1++)
Now for the body of the for loop. It says "If group2 is not blank, print group 2 which is a float. If gorup3 is not blank, print group3 which is an int. If group4 is not blank, print group 4 which is a string. If group5 is not blank, print group5 which is a bool. When inside a loop, the syntax is:
//group0 is some kind of "currently evaluating" string plus group matches.
//group1 is my key group
//group2/3/4/5 are my values groups float/int/string/bool.
theString = (*iteratorVariableName)[groupNumber]
I have in a log file some lines like this:
11-test.domain1.com Logged ...
37-user1.users.domain2.org Logged ...
48-me.server.domain3.net Logged ...
How can I extract each domain without the subdomains? Something between "-" and "Logged".
I have the following code in c++ (linux) but it doesn't extract well. Some function which is returning the extracted string would be great if you have some example of course.
regex_t preg;
regmatch_t mtch[1];
size_t rm, nmatch;
char tempstr[1024] = "";
int start;
rm=regcomp(&preg, "-[^<]+Logged", REG_EXTENDED);
nmatch = 1;
while(regexec(&preg, buffer+start, nmatch, mtch, 0)==0) /* Found a match */
{
strncpy(host, buffer+start+mtch[0].rm_so+3, mtch[0].rm_eo-mtch[0].rm_so-7);
printf("%s\n", tempstr);
start +=mtch[0].rm_eo;
memset(host, '\0', strlen(host));
}
regfree(&preg);
Thank you!
P.S. no, I cannot use perl for this because this part is inside of a larger c program which was made by someone else.
EDIT:
I replace the code with this one:
const char *p1 = strstr(buffer, "-")+1;
const char *p2 = strstr(p1, " Logged");
size_t len = p2-p1;
char *res = (char*)malloc(sizeof(char)*(len+1));
strncpy(res, p1, len);
res[len] = '\0';
which is extracting very good the whole domain including subdomains.
How can I extract just the domain.com or domain.net from abc.def.domain.com ?
is strtok a good option and how can I calculate which is the last dot ?
#include <vector>
#include <string>
#include <boost/regex.hpp>
int main()
{
boost::regex re(".+-(?<domain>.+)\\s*Logged");
std::string examples[] =
{
"11-test.domain1.com Logged ...",
"37-user1.users.domain2.org Logged ..."
};
std::vector<std::string> vec(examples, examples + sizeof(examples) / sizeof(*examples));
std::for_each(vec.begin(), vec.end(), [&re](const std::string& s)
{
boost::smatch match;
if (boost::regex_search(s, match, re))
{
std::cout << match["domain"] << std::endl;
}
});
}
http://liveworkspace.org/code/1983494e6e9e884b7e539690ebf98eb5
something like this with boost::regex. Don't know about pcre.
Is the in a standard format?
it appears so, is there a split function?
Edit:
Here is some logic.
Iterate through each domain to be parsed
Find a function to locate the index of the first string "-"
Next find the index of the second string minus the first string "Logged"
Now you have the full domain.
Once you have the full domain "Split" the domain into your object of choice (I used an array)
now that you have the array broken apart locate the index of the value you wish to reassemble (concatenate) to capture only the domain.
NOTE Written in C#
Main method which defines the first value and the second value
`static void Main(string[] args)
{
string firstValue ="-";
string secondValue = "Logged";
List domains = new List { "11-test.domain1.com Logged", "37-user1.users.domain2.org Logged","48-me.server.domain3.net Logged"};
foreach (string dns in domains)
{
Debug.WriteLine(Utility.GetStringBetweenFirstAndSecond(dns, firstValue, secondValue));
}
}
`
Method to parse the string:
`public string GetStringBetweenFirstAndSecond(string str, string firstStringToFind, string secondStringToFind)
{
string domain = string.Empty;
if(string.IsNullOrEmpty(str))
{
//throw an exception, return gracefully, whatever you determine
}
else
{
//This can all be done in one line, but I broke it apart so it can be better understood.
//returns the first occurrance.
//int start = str.IndexOf(firstStringToFind) + 1;
//int end = str.IndexOf(secondStringToFind);
//domain = str.Substring(start, end - start);
//i.e. Definitely not quite as legible, but doesn't create object unnecessarily
domain = str.Substring((str.IndexOf(firstStringToFind) + 1), str.IndexOf(secondStringToFind) - (str.IndexOf(firstStringToFind) + 1));
string[] dArray = domain.Split('.');
if (dArray.Length > 0)
{
if (dArray.Length > 2)
{
domain = string.Format("{0}.{1}", dArray[dArray.Length - 2], dArray[dArray.Length - 1]);
}
}
}
return domain;
}
`
If i have a string as such
"I am not here... \n..Hello\n.\n....Whats happening"
I want to replace the above string so:
"I am not here... \n..Hello\n. \n....Whats happening"
^ Space added
Just a bit of a background on what im doing. Im using sendmail in C++ and \n.\n is End Of Message Equivalent of sendmail. I just created a class that uses sendmail to send mails. but obviously if the user from the outsite gives sendmail that command i want it to be removed. Here is my message function just incase.:
//Operator to add to the message
void operator<<(string imessage){
if (imessage != ""){ message += imessage; }
}
How would i go about doing this. Thanks in advance :D
This is my last version :)
This code handles the case mentioned by #Greg Hewgill
string& format_text(string& str)
{
const string::size_type dot_offset = 2;
string::size_type found_at_start = str.find("\n.\n"),
found_at = str.find("\n.\n");
if(found_at_start != string::npos)
str.insert(0, " ");
while(found_at != string::npos)
{
str.insert(found_at+dot_offset+1, " ");
found_at = str.find("\n.\n", found_at+1);
}
return str;
}
int main()
{
string text = ".\nn\n.\nn";
std::cout << format_text(text);
}
Look up String.find and String.replace
For example (not tested)
string endOfMessage = "\n.\n";
string replacement = "\n. \n";
size_t position;
while (position = message.find(endOfMessage))
{
message.replace(position, endOfMessage.length(), replacement);
}
This is derived from Dan McG's answer so upvote him ;)
string endOfMessage = "\n.\n";
string replacement = "\n. \n";
size_t position;
while (position = message.find(endOfMessage, position) != message.npos)
{
message.replace(position, endOfMessage.length(), replacement);
position += replacement.length();
}
Boost has Boost.Regex (a regular expression module). Might be overkill if this is the only replacement you need to do.
Use std::search and the insert method of sequence containers such as string, deque, or whatever you use to store the message text.
typedef std::string::iterator SIter; // or whatever container you use
static const char *end_seq = "\n.\n";
for ( SIter tricky_begin = msg.begin();
tricky_begin = std::search( tricky_begin, msg.end(), end_seq, end_seq+3 ),
tricky_begin != msg.end(); ) {
tricky_begin = msg.insert( tricky_begin + 2, ' ' );
}
Given a string "filename.conf", how to I verify the extension part?
I need a cross platform solution.
Is this too simple of a solution?
#include <iostream>
#include <string>
int main()
{
std::string fn = "filename.conf";
if(fn.substr(fn.find_last_of(".") + 1) == "conf") {
std::cout << "Yes..." << std::endl;
} else {
std::cout << "No..." << std::endl;
}
}
The best way is to not write any code that does it but call existing methods. In windows, the PathFindExtension method is probably the simplest.
So why would you not write your own?
Well, take the strrchr example, what happens when you use that method on the following string "c:\program files\AppleGate.Net\readme"? Is ".Net\readme" the extension? It is easy to write something that works for a few example cases, but can be much harder to write something that works for all cases.
With C++17 and its std::filesystem::path::extension (the library is the successor to boost::filesystem) you would make your statement more expressive than using e.g. std::string.
#include <iostream>
#include <filesystem> // C++17
namespace fs = std::filesystem;
int main()
{
fs::path filePath = "my/path/to/myFile.conf";
if (filePath.extension() == ".conf") // Heed the dot.
{
std::cout << filePath.stem() << " is a valid type."; // Output: "myFile is a valid type."
}
else
{
std::cout << filePath.filename() << " is an invalid type."; // Output: e.g. "myFile.cfg is an invalid type"
}
}
See also std::filesystem::path::stem, std::filesystem::path::filename.
You have to make sure you take care of file names with more then one dot.
example: c:\.directoryname\file.name.with.too.many.dots.ext would not be handled correctly by strchr or find.
My favorite would be the boost filesystem library that have an extension(path) function
Assuming you have access to STL:
std::string filename("filename.conf");
std::string::size_type idx;
idx = filename.rfind('.');
if(idx != std::string::npos)
{
std::string extension = filename.substr(idx+1);
}
else
{
// No extension found
}
Edit: This is a cross platform solution since you didn't mention the platform. If you're specifically on Windows, you'll want to leverage the Windows specific functions mentioned by others in the thread.
Someone else mentioned boost but I just wanted to add the actual code to do this:
#include <boost/filesystem.hpp>
using std::string;
string texture = foo->GetTextureFilename();
string file_extension = boost::filesystem::extension(texture);
cout << "attempting load texture named " << texture
<< " whose extensions seems to be "
<< file_extension << endl;
// Use JPEG or PNG loader function, or report invalid extension
actually the STL can do this without much code, I advise you learn a bit about the STL because it lets you do some fancy things, anyways this is what I use.
std::string GetFileExtension(const std::string& FileName)
{
if(FileName.find_last_of(".") != std::string::npos)
return FileName.substr(FileName.find_last_of(".")+1);
return "";
}
this solution will always return the extension even on strings like "this.a.b.c.d.e.s.mp3" if it cannot find the extension it will return "".
Actually, the easiest way is
char* ext;
ext = strrchr(filename,'.')
One thing to remember: if '.' doesn't exist in filename, ext will be NULL.
I've stumbled onto this question today myself, even though I already had a working code I figured out that it wouldn't work in some cases.
While some people already suggested using some external libraries, I prefer to write my own code for learning purposes.
Some answers included the method I was using in the first place (looking for the last "."), but I remembered that on linux hidden files/folders start with ".".
So if file file is hidden and has no extension, the whole file name would be taken for extension.
To avoid that I wrote this piece of code:
bool getFileExtension(const char * dir_separator, const std::string & file, std::string & ext)
{
std::size_t ext_pos = file.rfind(".");
std::size_t dir_pos = file.rfind(dir_separator);
if(ext_pos>dir_pos+1)
{
ext.append(file.begin()+ext_pos,file.end());
return true;
}
return false;
}
I haven't tested this fully, but I think that it should work.
I'd go with boost::filesystem::extension (std::filesystem::path::extension with C++17) but if you cannot use Boost and you just have to verify the extension, a simple solution is:
bool ends_with(const std::string &filename, const std::string &ext)
{
return ext.length() <= filename.length() &&
std::equal(ext.rbegin(), ext.rend(), filename.rbegin());
}
if (ends_with(filename, ".conf"))
{ /* ... */ }
Using std::string's find/rfind solves THIS problem, but if you work a lot with paths then you should look at boost::filesystem::path since it will make your code much cleaner than fiddling with raw string indexes/iterators.
I suggest boost since it's a high quality, well tested, (open source and commercially) free and fully portable library.
For char array-type strings you can use this:
#include <ctype.h>
#include <string.h>
int main()
{
char filename[] = "apples.bmp";
char extension[] = ".jpeg";
if(compare_extension(filename, extension) == true)
{
// .....
} else {
// .....
}
return 0;
}
bool compare_extension(char *filename, char *extension)
{
/* Sanity checks */
if(filename == NULL || extension == NULL)
return false;
if(strlen(filename) == 0 || strlen(extension) == 0)
return false;
if(strchr(filename, '.') == NULL || strchr(extension, '.') == NULL)
return false;
/* Iterate backwards through respective strings and compare each char one at a time */
for(int i = 0; i < strlen(filename); i++)
{
if(tolower(filename[strlen(filename) - i - 1]) == tolower(extension[strlen(extension) - i - 1]))
{
if(i == strlen(extension) - 1)
return true;
} else
break;
}
return false;
}
Can handle file paths in addition to filenames. Works with both C and C++. And cross-platform.
If you use Qt library, you can give a try to QFileInfo's suffix()
Good answers but I see most of them has some problems:
First of all I think a good answer should work for complete file names which have their path headings, also it should work for linux or windows or as mentioned it should be cross platform. For most of answers; file names with no extension but a path with a folder name including dot, the function will fail to return the correct extension: examples of some test cases could be as follow:
const char filename1 = {"C:\\init.d\\doc"}; // => No extention
const char filename2 = {"..\\doc"}; //relative path name => No extention
const char filename3 = {""}; //emputy file name => No extention
const char filename4 = {"testing"}; //only single name => No extention
const char filename5 = {"tested/k.doc"}; // normal file name => doc
const char filename6 = {".."}; // parent folder => No extention
const char filename7 = {"/"}; // linux root => No extention
const char filename8 = {"/bin/test.d.config/lx.wize.str"}; // ordinary path! => str
"brian newman" suggestion will fail for filename1 and filename4.
and most of other answers which are based on reverse find will fail for filename1.
I suggest including the following method in your source:
which is function returning index of first character of extension or the length of given string if not found.
size_t find_ext_idx(const char* fileName)
{
size_t len = strlen(fileName);
size_t idx = len-1;
for(size_t i = 0; *(fileName+i); i++) {
if (*(fileName+i) == '.') {
idx = i;
} else if (*(fileName + i) == '/' || *(fileName + i) == '\\') {
idx = len - 1;
}
}
return idx+1;
}
you could use the above code in your c++ application like below:
std::string get_file_ext(const char* fileName)
{
return std::string(fileName).substr(find_ext_idx(fileName));
}
The last point in some cases the a folder is given to file name as argument and includes a dot in the folder name the function will return folder's dot trailing so better first to user check that the given name is a filename and not folder name.
This is a solution I came up with. Then, I noticed that it is similar to what #serengeor posted.
It works with std::string and find_last_of, but the basic idea will also work if modified to use char arrays and strrchr.
It handles hidden files, and extra dots representing the current directory. It is platform independent.
string PathGetExtension( string const & path )
{
string ext;
// Find the last dot, if any.
size_t dotIdx = path.find_last_of( "." );
if ( dotIdx != string::npos )
{
// Find the last directory separator, if any.
size_t dirSepIdx = path.find_last_of( "/\\" );
// If the dot is at the beginning of the file name, do not treat it as a file extension.
// e.g., a hidden file: ".alpha".
// This test also incidentally avoids a dot that is really a current directory indicator.
// e.g.: "alpha/./bravo"
if ( dotIdx > dirSepIdx + 1 )
{
ext = path.substr( dotIdx );
}
}
return ext;
}
Unit test:
int TestPathGetExtension( void )
{
int errCount = 0;
string tests[][2] =
{
{ "/alpha/bravo.txt", ".txt" },
{ "/alpha/.bravo", "" },
{ ".alpha", "" },
{ "./alpha.txt", ".txt" },
{ "alpha/./bravo", "" },
{ "alpha/./bravo.txt", ".txt" },
{ "./alpha", "" },
{ "c:\\alpha\\bravo.net\\charlie.txt", ".txt" },
};
int n = sizeof( tests ) / sizeof( tests[0] );
for ( int i = 0; i < n; ++i )
{
string ext = PathGetExtension( tests[i][0] );
if ( ext != tests[i][1] )
{
++errCount;
}
}
return errCount;
}
A NET/CLI version using System::String
System::String^ GetFileExtension(System::String^ FileName)
{
int Ext=FileName->LastIndexOf('.');
if( Ext != -1 )
return FileName->Substring(Ext+1);
return "";
}
_splitpath, _wsplitpath, _splitpath_s, _wsplitpath_w
This is Windows (Platform SDK) only
You can use strrchr() to find last occurence of .(dot) and get .(dot) based extensions files.
Check the below code for example.
#include<stdio.h>
void GetFileExtension(const char* file_name) {
int ext = '.';
const char* extension = NULL;
extension = strrchr(file_name, ext);
if(extension == NULL){
printf("Invalid extension encountered\n");
return;
}
printf("File extension is %s\n", extension);
}
int main()
{
const char* file_name = "c:\\.directoryname\\file.name.with.too.many.dots.ext";
GetFileExtension(file_name);
return 0;
}
Here's a function that takes a path/filename as a string and returns the extension as a string. It is all standard c++, and should work cross-platform for most platforms.
Unlike several other answers here, it handles the odd cases that windows' PathFindExtension handles, based on PathFindExtensions's documentation.
wstring get_file_extension( wstring filename )
{
size_t last_dot_offset = filename.rfind(L'.');
// This assumes your directory separators are either \ or /
size_t last_dirsep_offset = max( filename.rfind(L'\\'), filename.rfind(L'/') );
// no dot = no extension
if( last_dot_offset == wstring::npos )
return L"";
// directory separator after last dot = extension of directory, not file.
// for example, given C:\temp.old\file_that_has_no_extension we should return "" not "old"
if( (last_dirsep_offset != wstring::npos) && (last_dirsep_offset > last_dot_offset) )
return L"";
return filename.substr( last_dot_offset + 1 );
}
I use these two functions to get the extension and filename without extension:
std::string fileExtension(std::string file){
std::size_t found = file.find_last_of(".");
return file.substr(found+1);
}
std::string fileNameWithoutExtension(std::string file){
std::size_t found = file.find_last_of(".");
return file.substr(0,found);
}
And these regex approaches for certain extra requirements:
std::string fileExtension(std::string file){
std::regex re(".*[^\\.]+\\.([^\\.]+$)");
std::smatch result;
if(std::regex_match(file,result,re))return result[1];
else return "";
}
std::string fileNameWithoutExtension(std::string file){
std::regex re("(.*[^\\.]+)\\.[^\\.]+$");
std::smatch result;
if(std::regex_match(file,result,re))return result[1];
else return file;
}
Extra requirements that are met by the regex method:
If filename is like .config or something like this, extension will be an empty string and filename without extension will be .config.
If filename doesn't have any extension, extention will be an empty string, filename without extension will be the filename unchanged.
EDIT:
The extra requirements can also be met by the following:
std::string fileExtension(const std::string& file){
std::string::size_type pos=file.find_last_of('.');
if(pos!=std::string::npos&&pos!=0)return file.substr(pos+1);
else return "";
}
std::string fileNameWithoutExtension(const std::string& file){
std::string::size_type pos=file.find_last_of('.');
if(pos!=std::string::npos&&pos!=0)return file.substr(0,pos);
else return file;
}
Note:
Pass only the filenames (not path) in the above functions.
Try to use strstr
char* lastSlash;
lastSlash = strstr(filename, ".");
Or you can use this:
char *ExtractFileExt(char *FileName)
{
std::string s = FileName;
int Len = s.length();
while(TRUE)
{
if(FileName[Len] != '.')
Len--;
else
{
char *Ext = new char[s.length()-Len+1];
for(int a=0; a<s.length()-Len; a++)
Ext[a] = FileName[s.length()-(s.length()-Len)+a];
Ext[s.length()-Len] = '\0';
return Ext;
}
}
}
This code is cross-platform
So, using std::filesystem is the best answer, but if for whatever reason you don't have C++17 features available, this will work even if the input string includes directories:
string getextn (const string &fn) {
int sep = fn.find_last_of(".\\/");
return (sep >= 0 && fn[sep] == '.') ? fn.substr(sep) : "";
}
I'm adding this because the rest of the answers here are either strangely complicated or fail if the path to the file contains a dot and the file doesn't. I think the fact that find_last_of can look for multiple characters is often overlooked.
It works with both / and \ path separators. It fails if the extension itself contains a slash but that's usually too rare to matter. It doesn't do any filtering for filenames that start with a dot and contain no other dots -- if this matters to you then this is the least unreasonable answer here.
Example input / output:
/ => ''
./ => ''
./pathname/ => ''
./path.name/ => ''
pathname/ => ''
path.name/ => ''
c:\path.name\ => ''
/. => '.'
./. => '.'
./pathname/. => '.'
./path.name/. => '.'
pathname/. => '.'
path.name/. => '.'
c:\path.name\. => '.'
/.git_ignore => '.git_ignore'
./.git_ignore => '.git_ignore'
./pathname/.git_ignore => '.git_ignore'
./path.name/.git_ignore => '.git_ignore'
pathname/.git_ignore => '.git_ignore'
path.name/.git_ignore => '.git_ignore'
c:\path.name\.git_ignore => '.git_ignore'
/filename => ''
./filename => ''
./pathname/filename => ''
./path.name/filename => ''
pathname/filename => ''
path.name/filename => ''
c:\path.name\filename => ''
/filename. => '.'
./filename. => '.'
./pathname/filename. => '.'
./path.name/filename. => '.'
pathname/filename. => '.'
path.name/filename. => '.'
c:\path.name\filename. => '.'
/filename.tar => '.tar'
./filename.tar => '.tar'
./pathname/filename.tar => '.tar'
./path.name/filename.tar => '.tar'
pathname/filename.tar => '.tar'
path.name/filename.tar => '.tar'
c:\path.name\filename.tar => '.tar'
/filename.tar.gz => '.gz'
./filename.tar.gz => '.gz'
./pathname/filename.tar.gz => '.gz'
./path.name/filename.tar.gz => '.gz'
pathname/filename.tar.gz => '.gz'
path.name/filename.tar.gz => '.gz'
c:\path.name\filename.tar.gz => '.gz'
If you happen to use Poco libraries you can do:
#include <Poco/Path.h>
...
std::string fileExt = Poco::Path("/home/user/myFile.abc").getExtension(); // == "abc"
If you consider the extension as the last dot and the possible characters after it, but only if they don't contain the directory separator character, the following function returns the extension starting index, or -1 if no extension found. When you have that you can do what ever you want, like strip the extension, change it, check it etc.
long get_extension_index(string path, char dir_separator = '/') {
// Look from the end for the first '.',
// but give up if finding a dir separator char first
for(long i = path.length() - 1; i >= 0; --i) {
if(path[i] == '.') {
return i;
}
if(path[i] == dir_separator) {
return -1;
}
}
return -1;
}
I used PathFindExtension() function to know whether it is a valid tif file or not.
#include <Shlwapi.h>
bool A2iAWrapperUtility::isValidImageFile(string imageFile)
{
char * pStrExtension = ::PathFindExtension(imageFile.c_str());
if (pStrExtension != NULL && strcmp(pStrExtension, ".tif") == 0)
{
return true;
}
return false;
}