Mantid
Loading...
Searching...
No Matches
MultipleFileProperty.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
10
16
17#include <Poco/Path.h>
18#include <boost/algorithm/string.hpp>
19#include <boost/regex.hpp>
20
21#include <algorithm>
22#include <cctype>
23#include <functional>
24#include <numeric>
25
26using namespace Mantid::Kernel;
27using namespace Mantid::API;
28
29namespace // anonymous
30{
32Mantid::Kernel::Logger g_log("MultipleFileProperty");
33
38bool doesNotContainWildCard(const std::string &ext) { return std::string::npos == ext.find('*'); }
39
40static const std::string SUCCESS("");
41
42// Regular expressions for any adjacent + or , operators
43const std::string INVALID = R"(\+\+|,,|\+,|,\+)";
44static const boost::regex REGEX_INVALID(INVALID);
45
46// Regular expressions that represent the allowed instances of , operators
47const std::string NUM_COMMA_ALPHA(R"((?<=\d)\s*,\s*(?=\D))");
48const std::string ALPHA_COMMA_ALPHA(R"((?<=\D)\s*,\s*(?=\D))");
49const std::string COMMA_OPERATORS = NUM_COMMA_ALPHA + "|" + ALPHA_COMMA_ALPHA;
50static const boost::regex REGEX_COMMA_OPERATORS(COMMA_OPERATORS);
51
52// Regular expressions that represent the allowed instances of + operators
53const std::string NUM_PLUS_ALPHA(R"((?<=\d)\s*\+\s*(?=\D))");
54const std::string ALPHA_PLUS_ALPHA(R"((?<=\D)\s*\+\s*(?=\D))");
55const std::string PLUS_OPERATORS = NUM_PLUS_ALPHA + "|" + ALPHA_PLUS_ALPHA;
56static const boost::regex REGEX_PLUS_OPERATORS(PLUS_OPERATORS, boost::regex_constants::perl);
57
58bool isASCII(const std::string &str) {
59 return !std::any_of(str.cbegin(), str.cend(), [](char c) { return static_cast<unsigned char>(c) > 127; });
60}
61
62} // anonymous namespace
63
64namespace Mantid::API {
73MultipleFileProperty::MultipleFileProperty(const std::string &name, unsigned int action,
74 const std::vector<std::string> &exts, bool allowEmptyTokens)
75 : PropertyWithValue<std::vector<std::vector<std::string>>>(
76 name, std::vector<std::vector<std::string>>(),
77 std::make_shared<MultiFileValidator>(exts, (action == FileProperty::Load)), Direction::Input),
78 m_allowEmptyTokens(allowEmptyTokens) {
79 if (action != FileProperty::Load && action != FileProperty::OptionalLoad) {
81 throw std::runtime_error("Specified action is not supported for MultipleFileProperty");
82 } else {
83 m_action = action;
84 }
85
86 m_multiFileLoadingEnabled = Kernel::ConfigService::Instance().getValue<bool>("loading.multifile").value_or(false);
87 std::copy_if(exts.cbegin(), exts.cend(), std::back_inserter(m_exts), doesNotContainWildCard);
88}
89
96MultipleFileProperty::MultipleFileProperty(const std::string &name, const std::vector<std::string> &exts)
98
104
109 if (isOptional()) {
110 return SUCCESS;
111 } else {
112 return "No file specified.";
113 }
114}
115
128std::string MultipleFileProperty::setValue(const std::string &propValue) {
129 // No empty value is allowed, unless optional.
130 // This is yet aditional check that is beyond the underlying
131 // MultiFileValidator, so isOptional needs to be inspected here as well
132 if (propValue.empty() && !isOptional())
133 return "No file(s) specified.";
134
135 // If multiple file loading is disabled, then set value assuming it is a
136 // single file.
138 g_log.debug("MultiFile loading is not enabled, acting as standard FileProperty.");
139 return setValueAsSingleFile(propValue);
140 }
141
142 try {
143 // Else try and set the value, assuming it could be one or more files.
144 return setValueAsMultipleFiles(propValue);
145 } catch (const std::range_error &re) {
146 // it was a valid multi file string but for too many files.
147 return std::string(re.what());
148 } catch (const std::runtime_error &re) {
149 g_log.debug("MultiFile loading has failed. Trying as standard FileProperty.");
150
151 const std::string error = setValueAsSingleFile(propValue);
152
153 if (error.empty())
154 return SUCCESS;
155
156 // If we failed return the error message from the multiple file load attempt
157 // as the single file was a guess and probably not what the user will expect
158 // to see
159 return re.what();
160 }
161}
162
163std::string MultipleFileProperty::value() const {
165 return toString(m_value, "", "");
166
167 return toString(m_value);
168}
169
176 return toString(m_initialValue, "", "");
177
178 return toString(m_initialValue);
179}
180
190std::string MultipleFileProperty::setValueAsSingleFile(const std::string &propValue) {
191 // if value is unchanged use the cached version
192 if ((propValue == m_oldPropValue) && (!m_oldFoundValue.empty())) {
194 return SUCCESS;
195 }
196
197 // Use a temporary single FileProperty to do the job for us using this name
198 FileProperty singleFileProperty(this->name(), "", FileProperty::Load, m_exts, Direction::Input);
199
200 std::string error = singleFileProperty.setValue(propValue);
201
202 if (!error.empty())
203 return error;
204
205 // Store.
206 std::vector<std::vector<std::string>> foundFiles;
207 try {
208 toValue(singleFileProperty(), foundFiles, "", "");
210 } catch (std::invalid_argument &except) {
211 g_log.debug() << "Could not set property " << name() << ": " << except.what();
212 return except.what();
213 }
214
215 // cache the new version of things
216 m_oldPropValue = propValue;
217 m_oldFoundValue = std::move(foundFiles);
218
219 return SUCCESS;
220}
221
234std::string MultipleFileProperty::setValueAsMultipleFiles(const std::string &propValue) {
235 // if value is unchanged use the cached version
236 if ((propValue == m_oldPropValue) && (!m_oldFoundValue.empty())) {
238 return SUCCESS;
239 }
240
241 // Return error if there are any adjacent + or , operators.
242 boost::smatch invalid_substring;
243 if (!m_allowEmptyTokens && boost::regex_search(propValue.begin(), propValue.end(), invalid_substring, REGEX_INVALID))
244 return "Unable to parse filename due to an empty token.";
245 if (!isASCII(propValue))
246 return "Unable to parse filename due to an unsupported non-ASCII character being found.";
247
248 std::vector<std::vector<std::string>> fileNames;
249
250 // Tokenise on allowed comma operators, and iterate over each token.
251 boost::sregex_token_iterator end;
252 boost::sregex_token_iterator commaToken(propValue.begin(), propValue.end(), REGEX_COMMA_OPERATORS, -1);
253
254 for (; commaToken != end; ++commaToken) {
255 const std::string commaTokenString = commaToken->str();
256
257 // Tokenise on allowed plus operators.
258 boost::sregex_token_iterator plusToken(commaTokenString.begin(), commaTokenString.end(), REGEX_PLUS_OPERATORS, -1);
259
260 std::vector<std::vector<std::string>> temp;
261
262 // Put the tokens into a vector before iterating over it this time,
263 // so we can see how many we have.
264 std::vector<std::string> plusTokenStrings;
265 for (; plusToken != end; ++plusToken)
266 plusTokenStrings.emplace_back(plusToken->str());
267
268 m_parser.setTrimWhiteSpaces(autoTrim()); // keep trimming whitespaces in parser consistent with this property
269 for (auto &plusTokenString : plusTokenStrings) {
270 try {
271 m_parser.parse(plusTokenString);
272 } catch (const std::range_error &re) {
273 g_log.error(re.what());
274 throw;
275 } catch (const std::runtime_error &) {
276 // We should be able to safely ignore runtime_errors from parse(),
277 // see below.
278 }
279
280 std::vector<std::vector<std::string>> f = m_parser.fileNames();
281
282 // If there are no files, then we should keep this token as it was passed
283 // to the property, in its untampered form. This will enable us to deal
284 // with the case where a user is trying to load a single (and possibly
285 // existing) file within a token, but which has unexpected zero padding,
286 // or some other anomaly.
287 if (VectorHelper::flattenVector(f).empty())
288 f.emplace_back(1, plusTokenString);
289
290 if (plusTokenStrings.size() > 1) {
291 // See [3] in header documentation. Basically, for reasons of
292 // ambiguity, we cant add together plusTokens if they contain a range
293 // of files. So throw on any instances of this when there is more than
294 // plusToken.
295 if (f.size() > 1)
296 return "Adding a range of files to another file(s) is not currently "
297 "supported.";
298
299 if (temp.empty())
300 temp.emplace_back(f[0]);
301 else {
302 for (auto &parsedFile : f[0])
303 temp[0].emplace_back(parsedFile);
304 }
305 } else {
306 temp.insert(temp.end(), f.begin(), f.end());
307 }
308 }
309
310 fileNames.insert(fileNames.end(), std::make_move_iterator(temp.begin()), std::make_move_iterator(temp.end()));
311 }
312
313 std::vector<std::vector<std::string>> allUnresolvedFileNames = fileNames;
314 std::vector<std::vector<std::string>> allFullFileNames;
315
316 // First, find the default extension. Flatten all the unresolved filenames
317 // first, to make this easier.
318 std::vector<std::string> flattenedAllUnresolvedFileNames = VectorHelper::flattenVector(allUnresolvedFileNames);
319 std::string defaultExt;
320 for (const auto &unresolvedFileName : flattenedAllUnresolvedFileNames) {
321 try {
322 // Check for an extension.
323 Poco::Path path(unresolvedFileName);
324 if (!path.getExtension().empty()) {
325 defaultExt = "." + path.getExtension();
326 break;
327 }
328
329 } catch (Poco::Exception &) {
330 // Safe to ignore? Need a better understanding of the circumstances under
331 // which this throws.
332 }
333 }
334
335 // Cycle through each vector of unresolvedFileNames in allUnresolvedFileNames.
336 // Remember, each vector contains files that are to be added together.
337 for (const auto &unresolvedFileNames : allUnresolvedFileNames) {
338 // Check for the existance of wild cards. (Instead of iterating over all the
339 // filenames just join them together and search for "*" in the result.)
340 if (std::string::npos != boost::algorithm::join(unresolvedFileNames, "").find("*"))
341 return "Searching for files by wildcards is not currently supported.";
342
343 std::vector<std::string> fullFileNames;
344
345 for (const auto &unresolvedFileName : unresolvedFileNames) {
346 bool useDefaultExt;
347
348 try {
349 // Check for an extension.
350 Poco::Path path(unresolvedFileName);
351
352 useDefaultExt = path.getExtension().empty();
353 } catch (Poco::Exception &) {
354 // Just shove the problematic filename straight into FileProperty and
355 // see if we have any luck.
356 useDefaultExt = false;
357 }
358
359 std::string fullyResolvedFile;
360
361 if (!useDefaultExt) {
362 FileProperty slaveFileProp("Slave", "", FileProperty::Load, m_exts, Direction::Input);
363 std::string error = slaveFileProp.setValue(unresolvedFileName);
364
365 // If an error was returned then pass it along.
366 if (!error.empty()) {
367 throw std::runtime_error(error);
368 }
369
370 fullyResolvedFile = slaveFileProp();
371 } else {
372 // If a default ext has been specified/found, then use it.
373 std::string errors = "";
374 if (!defaultExt.empty()) {
375 auto run = FileFinder::Instance().findRun(unresolvedFileName, std::vector<std::string>(1, defaultExt));
376 if (run)
377 fullyResolvedFile = run.result();
378 else
379 errors += run.errors();
380
381 } else {
382 auto run = FileFinder::Instance().findRun(unresolvedFileName, m_exts);
383 if (run)
384 fullyResolvedFile = run.result();
385 else
386 errors += run.errors();
387 }
388 if (fullyResolvedFile.empty()) {
389 bool doThrow = false;
390 if (m_allowEmptyTokens) {
391 try {
392 const int unresolvedInt = std::stoi(unresolvedFileName);
393 if (unresolvedInt != 0) {
394 doThrow = true;
395 }
396 } catch (std::invalid_argument &) {
397 doThrow = true;
398 }
399 } else {
400 doThrow = true;
401 }
402 if (doThrow) {
403 auto errorMsg = "Unable to find file matching the string \"" + unresolvedFileName +
404 "\", please check the data search directories.";
405 if (!errors.empty())
406 errorMsg += " " + errors;
407
408 throw std::runtime_error(errorMsg);
409 } else {
410 // if the fullyResolvedFile is empty, it means it failed to find the
411 // file so keep the unresolvedFileName as a hint to be displayed
412 // later on in the error message
413 fullyResolvedFile = unresolvedFileName;
414 }
415 }
416 }
417
418 // Append the file name to result.
419 fullFileNames.emplace_back(std::move(fullyResolvedFile));
420 }
421 allFullFileNames.emplace_back(std::move(fullFileNames));
422 }
423
424 // Now re-set the value using the full paths found.
425 PropertyWithValue<std::vector<std::vector<std::string>>>::operator=(allFullFileNames);
426
427 // cache the new version of things
428 m_oldPropValue = propValue;
429 m_oldFoundValue = std::move(allFullFileNames);
430
431 return SUCCESS;
432}
433
434} // namespace Mantid::API
std::string name
Definition Run.cpp:60
double error
#define SUCCESS
A specialized class for dealing with file properties.
std::string setValue(const std::string &propValue) override
Overridden setValue method.
@ OptionalLoad
to specify a file to read but the file doesn't have to exist
@ Load
allowed here which will be passed to the algorithm
A property to allow a user to specify multiple files to load.
unsigned int m_action
The action type of this property Load (dafault) or OptionalLoad are supported.
std::string setValueAsSingleFile(const std::string &propValue)
Called by setValue in the case where a user has disabled multiple file loading.
std::string getDefault() const override
Get the value the property was initialised with -its default value.
bool isOptional() const
Check if this property is optional.
std::string value() const override
Returns the value of the property as a string.
std::string isEmptyValueValid() const
Returns a string depending on whether an empty value is valid.
std::vector< std::string > m_exts
Suggested extensions.
std::string setValueAsMultipleFiles(const std::string &propValue)
Called by setValue in the case where multiple file loading is enabled.
std::vector< std::vector< std::string > > m_oldFoundValue
Last value of the found files used in MultipleFileProperty::setValueAsMultipleFiles and MultipleFileP...
std::string setValue(const std::string &propValue) override
Convert the given propValue into a comma and plus separated list of full filenames,...
MultipleFileProperty(const std::string &name, unsigned int action, const std::vector< std::string > &exts=std::vector< std::string >(), bool allowEmptyTokens=false)
Alternative constructor with action.
std::string m_oldPropValue
Last value of propValue used in MultipleFileProperty::setValueAsMultipleFiles and MultipleFilePropert...
bool m_multiFileLoadingEnabled
Whether or not the user has turned on multifile loading.
bool m_allowEmptyTokens
Whether to allow for empty tokens.
Kernel::MultiFileNameParsing::Parser m_parser
Parser used to parse multi-file strings.
Loads a workspace from a data file.
Definition Load.h:23
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
void setTrimWhiteSpaces(const bool &setting)
Set the flag for trimming whitespaces in run string.
const std::vector< std::vector< std::string > > & fileNames() const
Return the vector of vectors of parsed file names.
void parse(const std::string &multiFileName)
Parse the given multiFileNameString.
The MultiFileValidator validates a MultiFileProperty, which contains a vector of vectors* of filename...
The concrete, templated class for properties.
std::vector< std::vector< std::string > > m_initialValue
the property's default value which is also its initial value
std::vector< std::vector< std::string > > m_value
The value of the property.
bool autoTrim() const
Returns if the property is set to automatically trim string unput values of whitespace.
Definition Property.cpp:355
const std::string & name() const
Get the property's name.
Definition Property.cpp:61
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::vector< T > flattenVector(const std::vector< std::vector< T > > &v)
A convenience function to "flatten" the given vector of vectors into a single vector.
void toValue(const std::string &strvalue, T &value)
std::string toString(const T &value)
Convert values to strings.
STL namespace.
Describes the direction (within an algorithm) of a Property.
Definition Property.h:50
@ Input
An input workspace.
Definition Property.h:53