-
Nick Draper authoredNick Draper authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
MultiFileNameParser.cpp 30.60 KiB
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidKernel/MultiFileNameParser.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/FacilityInfo.h"
#include "MantidKernel/InstrumentInfo.h"
#include "MantidKernel/Exception.h"
#include <algorithm>
#include <numeric>
#include <iterator>
#include <cassert>
#include <ctype.h>
#include <cctype>
#include <sstream>
#include <boost/regex.hpp>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <boost/bind.hpp>
#include <Poco/Path.h>
namespace Mantid
{
namespace Kernel
{
namespace MultiFileNameParsing
{
/////////////////////////////////////////////////////////////////////////////
// Static constants.
/////////////////////////////////////////////////////////////////////////////
namespace Regexs
{
const std::string INST = "([A-Za-z]+|PG3|pg3)";
const std::string UNDERSCORE = "(_{0,1})";
const std::string SPACE = "(\\s*)";
const std::string COMMA = "(" + SPACE + "," + SPACE + ")";
const std::string PLUS = "(" + SPACE + "\\+" + SPACE + ")";
const std::string MINUS = "(" + SPACE + "\\-" + SPACE + ")";
const std::string COLON = "(" + SPACE + ":" + SPACE + ")";
const std::string SINGLE = "(" + INST + "*[0-9]+)";
const std::string RANGE = "(" + SINGLE + COLON + SINGLE + ")";
const std::string STEP_RANGE = "(" + SINGLE + COLON + SINGLE + COLON + SINGLE + ")";
const std::string ADD_LIST = "(" + SINGLE + "(" + PLUS + SINGLE + ")+" + ")";
const std::string ADD_RANGE = "(" + SINGLE + MINUS + SINGLE + ")";
const std::string ADD_STEP_RANGE = "(" + SINGLE + MINUS + SINGLE + COLON + SINGLE + ")";
const std::string ANY = "(" + ADD_STEP_RANGE + "|" + ADD_RANGE + "|" + ADD_LIST + "|" + STEP_RANGE + "|" + RANGE + "|" + SINGLE + ")";
const std::string LIST = "(" + ANY + "(" + COMMA + ANY + ")*" + ")";
}
/////////////////////////////////////////////////////////////////////////////
// Forward declarations.
/////////////////////////////////////////////////////////////////////////////
namespace
{
// Anonymous helper functions.
std::vector<std::vector<unsigned int> > & parseToken(std::vector<std::vector<unsigned int> > & parsedRuns, const std::string & token);
std::vector<std::vector<unsigned int> > generateRange(unsigned int from, unsigned int to, unsigned int stepSize, bool addRuns);
void validateToken(const std::string & token);
bool matchesFully(const std::string & stringToMatch, const std::string & regexString, bool caseless = false);
std::string getMatchingString(const std::string & regexString, const std::string & toParse, bool caseless = false);
std::string pad(unsigned int run, const std::string& instString);
std::set< std::pair<unsigned int, unsigned int> > & mergeAdjacentRanges(
std::set< std::pair<unsigned int, unsigned int> > & ranges,
const std::pair<unsigned int, unsigned int> & range);
// Helper functor.
struct RangeContainsRun
{
bool operator()(std::pair<unsigned int, unsigned int> range, unsigned int run);
bool operator()(unsigned int run, std::pair<unsigned int, unsigned int> range);
};
std::string toString(const RunRangeList & runRangeList);
std::string & accumulateString(std::string & output, std::pair<unsigned int, unsigned int> runRange);
}
/////////////////////////////////////////////////////////////////////////////
// Scoped, global functions.
/////////////////////////////////////////////////////////////////////////////
/**
* Parses a string containing a comma separated list of run "tokens", where each run
* token is of one of the allowed forms (a single run or a range of runs or an added
* range of runs, etc.)
*
* @param runString :: a string containing the runs to parse, in the correct format.
*
* @returns a vector of vectors of unsigned ints, one int for each run, where runs
* to be added are contained in the same sub-vector.
* @throws std::runtime_error when runString provided is in an incorrect format.
*/
std::vector<std::vector<unsigned int> > parseMultiRunString(std::string runString)
{
// If the run string is empty, return no runs.
if(runString.empty())
return std::vector<std::vector<unsigned int> >();
// Remove whitespace.
runString.erase(std::remove_if( // ("Erase-remove" idiom.)
runString.begin(), runString.end(),
isspace),
runString.end());
// Only numeric characters, or occurances of plus, minus, comma and colon are allowed.
if(!matchesFully(runString,"([0-9]|\\+|\\-|,|:)+"))
{
throw std::runtime_error(
"Non-numeric or otherwise unaccetable character(s) detected.");
}
// Tokenize on commas.
std::vector<std::string> tokens;
tokens = boost::split(tokens, runString, boost::is_any_of(","));
// Validate each token.
std::for_each(
tokens.begin(), tokens.end(),
validateToken);
// Parse each token, accumulate the results, and return them.
return std::accumulate(
tokens.begin(), tokens.end(),
std::vector<std::vector<unsigned int> >(),
parseToken);
}
/**
* Suggests a workspace name for the given vector of file names (which, because they
* are in the same vector, we will assume they are to be added.) Example:
*
* Parsing ["INST_4.ext", "INST_5.ext", "INST_6.ext", "INST_8.ext"] will return
* "INST_4_to_6_and_8" as a suggested workspace name.
*
* @param fileNames :: a vector of file names
*
* @returns a string containing a suggested workspace name.
* @throws std::runtime_error when runString provided is in an incorrect format.
*/
std::string suggestWorkspaceName(const std::vector<std::string> & fileNames)
{
Parser parser;
RunRangeList runs;
// For each file name, parse the run number out of it, and add it to a RunRangeList.
for(size_t i = 0; i < fileNames.size(); ++i)
{
parser.parse(fileNames[i]);
runs.addRun(parser.runs()[0][0]);
}
// Return the suggested ws name.
return parser.instString() + parser.underscoreString() + toString(runs);
}
/////////////////////////////////////////////////////////////////////////////
// Comparator class.
/////////////////////////////////////////////////////////////////////////////
/**
* Comparator for the set that holds instrument names in Parser. This is reversed
* since we want to come across the longer instrument names first. It is caseless
* so we don't get "inst" coming before "INSTRUMENT" - though this is probably overkill.
*/
bool ReverseCaselessCompare::operator()(const std::string & a, const std::string & b)
{
std::string lowerA(a);
std::string lowerB(b);
std::transform(lowerA.begin(), lowerA.end(), lowerA.begin(), tolower);
std::transform(lowerB.begin(), lowerB.end(), lowerB.begin(), tolower);
return lowerA > lowerB;
}
/////////////////////////////////////////////////////////////////////////////
// Public member functions of Parser class.
/////////////////////////////////////////////////////////////////////////////
/// Constructor.
Parser::Parser() :
m_runs(), m_fileNames(), m_multiFileName(), m_dirString(), m_instString(),
m_underscoreString(), m_runString(), m_extString(),
//m_zeroPadding(),
m_validInstNames()
{
ConfigServiceImpl & config = ConfigService::Instance();
auto facilities = config.getFacilities();
for( auto itFacility = facilities.begin(); itFacility != facilities.end(); ++itFacility )
{
const std::vector<InstrumentInfo> instruments = (**itFacility).instruments();
for( auto instrument = instruments.begin(); instrument != instruments.end(); ++instrument )
{
m_validInstNames.insert(instrument->name());
m_validInstNames.insert(instrument->shortName());
}
}
}
/// Destructor.
Parser::~Parser()
{}
/**
* Takes the given multiFileName string, and calls other parts of the parser
* to generate a corresponding vector of vectors of file names.
*
* @param multiFileName :: the string containing the multiple file names to be parsed.
*/
void Parser::parse(const std::string & multiFileName)
{
// Clear any contents of the member variables.
clear();
// Set the string to parse.
m_multiFileName = multiFileName;
// Split the string to be parsed into sections, and do some validation.
split();
// Parse the run section into unsigned ints we can use.
m_runs = parseMultiRunString(m_runString);
// Set up helper functor.
GenerateFileName generateFileName(
m_dirString,
m_extString,
m_instString);
// Generate complete file names for each run using helper functor.
std::transform(
m_runs.begin(), m_runs.end(),
std::back_inserter(m_fileNames),
generateFileName);
}
/////////////////////////////////////////////////////////////////////////////
// Private member functions of Parser class.
/////////////////////////////////////////////////////////////////////////////
/**
* Clears all member variables.
*/
void Parser::clear()
{
m_runs.clear();
m_fileNames.clear();
m_multiFileName.clear();
m_dirString.clear();
m_instString.clear();
m_underscoreString.clear();
m_runString.clear();
m_extString.clear();
}
/**
* Splits up the m_multiFileName string into component parts, to be used elsewhere by
* the parser. Some validation is done here, and exceptions thrown if required
* components are missing.
*
* @throws std::runtime_error if a required component is not present in the string.
*/
void Parser::split()
{
if(m_multiFileName.empty())
throw std::runtime_error("No file name to parse.");
// (We shun the use of Poco::File here as it is unable to deal with certain
// combinations of special characters, for example double commas.)
// Get the extension, if there is one.
size_t lastDot = m_multiFileName.find_last_of(".");
if(lastDot != std::string::npos)
m_extString = m_multiFileName.substr(lastDot);
// Get the directory, if there is one.
size_t lastSeparator = m_multiFileName.find_last_of("/\\");
if(lastSeparator != std::string::npos)
m_dirString = m_multiFileName.substr(0, lastSeparator + 1);
// If the directory contains an instance of a comma, then the string is
// a comma separated list of single *full* file names to load.
if(std::string::npos != m_dirString.find(","))
throw std::runtime_error("Unable to parse.");
// Slice off the directory and extension.
std::string base = m_multiFileName.substr(
m_dirString.size(), m_multiFileName.size() - (m_dirString.size() + m_extString.size()));
if( base.empty() )
throw std::runtime_error("There does not appear to be any runs present.");
// See if the user has typed in one of the available instrument names.
for( auto instName = m_validInstNames.begin(); instName != m_validInstNames.end(); ++instName )
{
// USE CASELESS MATCHES HERE.
if(matchesFully(base, *instName + ".*", true))
{
m_instString = getMatchingString("^" + *instName, base, true);
break;
}
}
// If not, use the default, or throw if we encounter an unrecognisable non-numeric string.
if( m_instString.empty() )
{
if( base.empty() )
throw std::runtime_error("There does not appear to be any runs present.");
if( isdigit(base[0]) )
m_instString = ConfigService::Instance().getString("default.instrument");
else
throw std::runtime_error("There does not appear to be a valid instrument name present.");
}
else
{
// Chop off instrument name.
base = base.substr(m_instString.size(), base.size());
}
if( base.empty() )
throw std::runtime_error("There does not appear to be any runs present.");
InstrumentInfo instInfo = ConfigService::Instance().getInstrument(m_instString);
// why?
//m_instString = instInfo.shortName(); // Make sure we're using the shortened form of the isntrument name.
if(boost::starts_with(base, instInfo.delimiter()))
{
// Store the instrument delimiter, and strip it off the start of the string.
m_underscoreString = instInfo.delimiter();
base = base.substr(m_underscoreString.size(), base.size());
}
m_runString = getMatchingString("^" + Regexs::LIST, base);
const std::string remainder = base.substr(m_runString.size(), base.size());
if( ! remainder.empty() )
{
throw std::runtime_error("There is an unparsable token present.");
}
}
/////////////////////////////////////////////////////////////////////////////
// Helper functor.
/////////////////////////////////////////////////////////////////////////////
/**
* Constructor, to accept state used in generating file names.
*
* @param prefix :: a string that prefixes the generated file names.
* @param suffix :: a string that suffixes the generated file names.
* @param instString :: the instrument name
*/
GenerateFileName::GenerateFileName(const std::string & prefix, const std::string & suffix, const std::string & instString) :
m_prefix(prefix), m_suffix(suffix), m_instString(instString)
{}
/**
* Overloaded function operator that takes in a vector of runs, and returns a vector of file names.
*
* @param runs :: the vector of runs with which to make file names.
*
* @returns the generated vector of file names.
*/
std::vector<std::string> GenerateFileName::operator()(const std::vector<unsigned int> & runs)
{
std::vector<std::string> fileNames;
std::transform(
runs.begin(), runs.end(),
std::back_inserter(fileNames),
(*this) // Call other overloaded function operator.
);
return fileNames;
}
/**
* Overloaded function operator that takes in a runs, and returns a file name.
*
* @param run :: the vector of runs with which to make file names.
*
* @returns the generated vector of file names.
*/
std::string GenerateFileName::operator()(unsigned int run)
{
std::stringstream fileName;
fileName << m_prefix
<< pad(run, m_instString)
<< m_suffix;
return fileName.str();
}
/////////////////////////////////////////////////////////////////////////////
// Public member functions of RunRangeList class.
/////////////////////////////////////////////////////////////////////////////
/**
* Default constructor.
*/
RunRangeList::RunRangeList() :
m_rangeList()
{
}
/**
* Adds a run to the list of run ranges. Not particularly effecient.
*
* @param run :: the run to add.
*/
void RunRangeList::addRun(unsigned int run)
{
// If the run is inside one of the ranges, do nothing.
if(std::binary_search(
m_rangeList.begin(), m_rangeList.end(),
run,
RangeContainsRun()))
return;
// Else create a new range, containing a single run, and add it to the list.
m_rangeList.insert(std::make_pair(run, run));
// Now merge any ranges that are adjacent.
m_rangeList = std::accumulate(
m_rangeList.begin(), m_rangeList.end(),
std::set< std::pair<unsigned int, unsigned int> >(),
mergeAdjacentRanges);
}
/**
* Adds a range of runs of specified length to the list of run ranges.
*
* @param from :: the beginning of the run to add
* @param to :: the end of the run to add
*/
void RunRangeList::addRunRange(unsigned int from, unsigned int to)
{
for( ; from <= to; ++from)
addRun(from);
}
/**
* Add a range of runs to the list of run ranges.
*
* @param range :: the range to add
*/
void RunRangeList::addRunRange(std::pair<unsigned int, unsigned int> range)
{
addRunRange(range.first, range.second);
}
/////////////////////////////////////////////////////////////////////////////
// Anonymous helper functions.
/////////////////////////////////////////////////////////////////////////////
namespace // anonymous
{
/**
* Parses a string containing a run "token".
*
* Note that this function takes the form required by the "accumulate" algorithm:
* it takes in the parsed runs so far and a new token to parse, and then returns
* the result of appending the newly parsed token to the already parsed runs.
*
* @param parsedRuns :: the vector of vectors of runs parsed so far.
* @param token :: the token to parse.
*
* @returns the newly parsed runs appended to the previously parsed runs.
* @throws std::runtime_error if
*/
std::vector<std::vector<unsigned int> > & parseToken(
std::vector<std::vector<unsigned int> > & parsedRuns, const std::string & token)
{
// Tokenise further, on plus, minus or colon.
std::vector<std::string> subTokens;
subTokens = boost::split(subTokens, token, boost::is_any_of("+-:"));
std::vector<unsigned int> rangeDetails;
// Convert the sub tokens to uInts.
std::vector<std::string>::iterator iter;
for (iter=subTokens.begin(); iter != subTokens.end(); ++iter)
{
try
{
rangeDetails.push_back(boost::lexical_cast<unsigned int>(*iter));
}
catch (boost::bad_lexical_cast &)
{
rangeDetails.push_back(0);
}
}
// We should always end up with at least 1 unsigned int here.
assert(1 <= rangeDetails.size());
std::vector<std::vector<unsigned int> > runs;
// E.g. "2012".
if(matchesFully(token, Regexs::SINGLE))
{
runs.push_back(std::vector<unsigned int>(1, rangeDetails[0]));
}
// E.g. "2012:2020".
else if(matchesFully(token, Regexs::RANGE))
{
runs = generateRange(
rangeDetails[0],
rangeDetails[1],
1,
false
);
}
// E.g. "2012:2020:4".
else if(matchesFully(token, Regexs::STEP_RANGE))
{
runs = generateRange(
rangeDetails[0],
rangeDetails[1],
rangeDetails[2],
false
);
}
// E.g. "2012+2013+2014+2015".
else if(matchesFully(token, Regexs::ADD_LIST))
{
// No need to generate the range here, it's already there for us.
runs = std::vector<std::vector<unsigned int> >(1, rangeDetails);
}
// E.g. "2012-2020".
else if(matchesFully(token, Regexs::ADD_RANGE))
{
runs = generateRange(
rangeDetails[0],
rangeDetails[1],
1,
true
);
}
// E.g. "2012-2020:4".
else if(matchesFully(token, Regexs::ADD_STEP_RANGE))
{
runs = generateRange(
rangeDetails[0],
rangeDetails[1],
rangeDetails[2],
true
);
}
else
{
// We should never reach here - the validation done on the token previously
// should prevent any other possible scenario.
assert(false);
}
// Add the runs on to the end of parsedRuns, and return it.
std::copy(
runs.begin(), runs.end(),
std::back_inserter(parsedRuns));
return parsedRuns;
}
/**
* Generates a range of runs between the given numbers, increasing
* or decreasing by the given step size. If addRuns is true, then the
* runs will all be in the same sub vector, if false they will each be
* in their own vector.
*
* @param from :: the start of the range
* @param to :: the end of the range
* @param stepSize :: the size of the steps with which to increase/decrease
* @param addRuns :: whether or not to add the runs together (place in sume sub-vector)
*
* @returns a vector of vectors of runs.
* @throws std::runtime_error if a step size of zero is specified.
*/
std::vector<std::vector<unsigned int> > generateRange(
unsigned int from,
unsigned int to,
unsigned int stepSize,
bool addRuns)
{
if(stepSize == 0)
throw std::runtime_error(
"Unable to generate a range with a step size of zero.");
size_t limit = 100;
int success = ConfigService::Instance().getValue("loading.multifilelimit",limit);
if (!success)
{
limit = 100;
}
unsigned int orderedTo = from>to?from:to;
unsigned int orderedFrom = from>to?to:from;
unsigned int numberOfFiles = (orderedTo-orderedFrom)/stepSize;
if (numberOfFiles>limit)
{
std::stringstream sstream;
sstream << "The range from " << orderedFrom << " to " << orderedTo
<< " step " << stepSize << ", would genetate " << numberOfFiles << " files. "
<< "This is greater then the current limit of " << limit << ". "
<< "This limit can be configured in the Mantid.user.properties file using the key loading.multifilelimit=200.";
throw std::range_error(sstream.str());
}
unsigned int currentRun = from;
std::vector<std::vector<unsigned int> > runs;
// If ascending range
if(from <= to)
{
while(currentRun <= to)
{
if(addRuns)
{
if(runs.empty())
runs.push_back(std::vector<unsigned int>(1, currentRun));
else
runs.at(0).push_back(currentRun);
}
else
{
runs.push_back(std::vector<unsigned int>(1, currentRun));
}
currentRun += stepSize;
}
}
// Else descending range
else
{
while(currentRun >= to)
{
if(addRuns)
{
if(runs.empty())
runs.push_back(std::vector<unsigned int>(1, currentRun));
else
runs.at(0).push_back(currentRun);
}
else
{
runs.push_back(std::vector<unsigned int>(1, currentRun));
}
// Guard against case where stepSize would take us into negative
// numbers (not supported by unsigned ints ...).
if(static_cast<int>(currentRun) - static_cast<int>(stepSize) < 0)
break;
currentRun -= stepSize;
}
}
return runs;
}
/**
* Validates the given run token.
*
* @param :: the run token to validate.
*
* @throws std::runtime_error if the token is of an incorrect form.
*/
void validateToken(const std::string & token)
{
// Each token must be non-empty.
if(token.size() == 0)
throw std::runtime_error(
"A comma-separated token is empty.");
// Each token must begin and end with a numeric character.
if(!matchesFully(token, "[0-9].+[0-9]|[0-9]"))
throw std::runtime_error(
"The token \"" + token + "\" is of an incorrect form. Does it begin or end with a plus, minus or colon?");
// Each token must be one of the acceptable forms, i.e. a single run, an added range of runs, etc.
if(!matchesFully(token, Regexs::ANY))
throw std::runtime_error(
"The token \"" + token + "\" is of an incorrect form.");
}
/**
* Convenience function that matches a *complete* given string to the given regex.
*
* @param stringToMatch :: the string to match with the given regex.
* @param regexString :: the regex with which to match the given string.
*
* @returns true if the string matches fully, or false otherwise.
*/
bool matchesFully(const std::string & stringToMatch, const std::string & regexString, bool caseless)
{
boost::regex regex;
if( caseless )
regex = boost::regex("^(" + regexString + "$)", boost::regex::icase);
else
regex = boost::regex("^(" + regexString + "$)");
return boost::regex_match(stringToMatch, regex);
}
/**
* Finds a given regex in a given string, and returns the part of the string
* that matches the regex. Returns "" if there is no occurance of the regex.
*
* @param regex - the regular expression to find within the string
* @param toParse - the string within to find the regex
*
* @returns the part (if any) of the given string that matches the given regex
*/
std::string getMatchingString(const std::string & regexString, const std::string & toParse, bool caseless)
{
boost::regex regex;
if( caseless )
{
regex = boost::regex(regexString, boost::regex::icase);
}
else
{
regex = boost::regex(regexString);
}
boost::sregex_iterator it(
toParse.begin(), toParse.end(),
regex
);
if(it == boost::sregex_iterator())
return "";
return it->str();
}
/**
* Zero pads the run number used in a file name to required length.
*
* @param run - the run number of the file.
* @param instString - the name of the instrument
*
* @returns the string, padded to the required length.
* @throws std::runtime_error if run is longer than size of count.
*/
std::string pad(unsigned int run, const std::string& instString)
{
InstrumentInfo instInfo = ConfigService::Instance().getInstrument(instString);
std::string prefix = instInfo.filePrefix( run ) + instInfo.delimiter();
unsigned int padLength = instInfo.zeroPadding( run );
std::string runStr = boost::lexical_cast<std::string>( run );
if(runStr.size() < padLength)
runStr.insert(0, padLength - runStr.size(), '0');
else if(padLength > 0 && runStr.size() > padLength)
throw std::runtime_error("Could not parse run number \"" + runStr +
"\" since the instrument run number length required is " + boost::lexical_cast<std::string>(padLength));
runStr.insert(0, prefix);
return runStr;
}
/**
* Overloaded function operators to be used by std::binary_search to test if a range and a run
* are "equivalent", which in this case we choose to mean whether or not the run is
* inside the range.
*/
bool RangeContainsRun::operator()(std::pair<unsigned int, unsigned int> range, unsigned int run)
{
return range.second < run;
}
bool RangeContainsRun::operator()(unsigned int run, std::pair<unsigned int, unsigned int> range)
{
return run < range.first;
}
/**
* Function for use with std::accumulate, the goal of which is merge any ranges
* that are adjacent.
*
* @param ranges :: the set of ranges that have been accumulated so far.
* @param range :: the range to add, or merge if it is adjacent
*
* @returns the original ranges, with the extra range added/merged.
*/
std::set< std::pair<unsigned int, unsigned int> > & mergeAdjacentRanges(
std::set< std::pair<unsigned int, unsigned int> > & ranges,
const std::pair<unsigned int, unsigned int> & range)
{
// If ranges is empty, just insert the new range.
if(ranges.empty())
{
ranges.insert(range);
}
// Else there are already some ranges present ...
else
{
// ... if the last one is adjacent to the new range, merge the two.
if(ranges.rbegin()->second + 1 == range.first)
{
unsigned int from = ranges.rbegin()->first;
unsigned int to = range.second;
std::pair<unsigned int, unsigned int> temp(from, to);
ranges.erase(--ranges.end(), ranges.end());
ranges.insert(temp);
}
// ... else just insert it.
else
{
ranges.insert(range);
}
}
return ranges;
}
/**
* Function for use with std::accumulate. Takes in a runRange, and appends its details onto the
* accumulated output string so far.
*
* @param output :: the accumulate output so far.
* @param runRange :: the range who's details should be appended.
*
* @returns the updated output
*/
std::string & accumulateString(std::string & output, std::pair<unsigned int, unsigned int> runRange)
{
if(!output.empty())
output += "_and_";
if(runRange.first == runRange.second)
output += boost::lexical_cast<std::string>(runRange.first);
else
output += boost::lexical_cast<std::string>(runRange.first) + "_to_" + boost::lexical_cast<std::string>(runRange.second);
return output;
}
/**
* Converts a RunRangeList object into a readable (and wsName-friendly) string.
*
* @param runRangeList :: the runRangeList to convert to a string
*
* @returns the converted string.
*/
std::string toString(const RunRangeList & runRangeList)
{
std::set<std::pair<unsigned int, unsigned int> > runRanges = runRangeList.rangeList();
// For each run range (pair of unsigned ints), call accumulateString and return the accumulated result.
return std::accumulate(
runRanges.begin(), runRanges.end(),
std::string(),
accumulateString);
}
} // anonymous namespace
} // namespace MultiFileNameParsing
} // namespace Kernel
} // namespace Mantid