summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/softlist.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/softlist.cpp')
-rw-r--r--src/emu/softlist.cpp311
1 files changed, 199 insertions, 112 deletions
diff --git a/src/emu/softlist.cpp b/src/emu/softlist.cpp
index 690bb47415f..c6bb7161be3 100644
--- a/src/emu/softlist.cpp
+++ b/src/emu/softlist.cpp
@@ -15,6 +15,7 @@
#include "expat.h"
+#include <array>
#include <cstring>
#include <regex>
@@ -24,7 +25,7 @@
// STATIC VARIABLES
//**************************************************************************
-static std::regex s_potenial_softlist_regex("\\w+(\\:\\w+)*");
+static std::regex s_potential_softlist_regex("\\w+(\\:\\w+)*");
//**************************************************************************
@@ -32,11 +33,10 @@ static std::regex s_potenial_softlist_regex("\\w+(\\:\\w+)*");
//**************************************************************************
//-------------------------------------------------
-// feature_list_item - constructor
+// software_info_item - constructor
//-------------------------------------------------
-feature_list_item::feature_list_item(const std::string &name, const std::string &value) :
- m_next(nullptr),
+software_info_item::software_info_item(const std::string &name, const std::string &value) :
m_name(name),
m_value(value)
{
@@ -44,11 +44,10 @@ feature_list_item::feature_list_item(const std::string &name, const std::string
//-------------------------------------------------
-// feature_list_item - constructor
+// software_info_item - constructor
//-------------------------------------------------
-feature_list_item::feature_list_item(std::string &&name, std::string &&value) :
- m_next(nullptr),
+software_info_item::software_info_item(std::string &&name, std::string &&value) :
m_name(std::move(name)),
m_value(std::move(value))
{
@@ -64,7 +63,6 @@ feature_list_item::feature_list_item(std::string &&name, std::string &&value) :
//-------------------------------------------------
software_part::software_part(software_info &info, std::string &&name, std::string &&interface) :
- m_next(nullptr),
m_info(info),
m_name(std::move(name)),
m_interface(std::move(interface))
@@ -77,17 +75,11 @@ software_part::software_part(software_info &info, std::string &&name, std::strin
// feature, if specified
//-------------------------------------------------
-const char *software_part::feature(const std::string &feature_name) const noexcept
+const char *software_part::feature(std::string_view feature_name) const noexcept
{
// scan the feature list for an entry matching feature_name and return the value
- auto iter = std::find_if(
- m_featurelist.begin(),
- m_featurelist.end(),
- [&feature_name] (const feature_list_item &feature) { return feature.name() == feature_name; });
-
- return iter != m_featurelist.end()
- ? iter->value().c_str()
- : nullptr;
+ auto const iter = m_features.find(feature_name);
+ return (iter != m_features.end()) ? iter->value().c_str() : nullptr;
}
@@ -126,16 +118,16 @@ bool software_part::matches_interface(const char *interface_list) const noexcept
// software_info - constructor
//-------------------------------------------------
-software_info::software_info(std::string &&name, std::string &&parent, const std::string &supported) :
- m_supported(SOFTWARE_SUPPORTED_YES),
+software_info::software_info(std::string &&name, std::string &&parent, std::string_view supported) :
+ m_supported(software_support::SUPPORTED),
m_shortname(std::move(name)),
m_parentname(std::move(parent))
{
// handle the supported flag if provided
if (supported == "partial")
- m_supported = SOFTWARE_SUPPORTED_PARTIAL;
+ m_supported = software_support::PARTIALLY_SUPPORTED;
else if (supported == "no")
- m_supported = SOFTWARE_SUPPORTED_NO;
+ m_supported = software_support::UNSUPPORTED;
}
@@ -144,13 +136,13 @@ software_info::software_info(std::string &&name, std::string &&parent, const std
// optional interface match
//-------------------------------------------------
-const software_part *software_info::find_part(const std::string &part_name, const char *interface) const
+const software_part *software_info::find_part(std::string_view part_name, const char *interface) const
{
// look for the part by name and match against the interface if provided
auto iter = std::find_if(
m_partdata.begin(),
m_partdata.end(),
- [&](const software_part &part)
+ [&part_name, interface] (const software_part &part)
{
// try to match the part_name (or all parts if part_name is empty), and then try
// to match the interface (or all interfaces if interface is nullptr)
@@ -184,29 +176,105 @@ bool software_info::has_multiple_parts(const char *interface) const
}
+namespace detail {
+
//**************************************************************************
// SOFTWARE LIST PARSER
//**************************************************************************
+class softlist_parser
+{
+public:
+ // construction (== execution)
+ softlist_parser(
+ util::read_stream &file,
+ std::string_view filename,
+ std::string &listname,
+ std::string &description,
+ std::list<software_info> &infolist,
+ std::ostream &errors);
+
+private:
+ enum parse_position
+ {
+ POS_ROOT,
+ POS_MAIN,
+ POS_SOFT,
+ POS_PART,
+ POS_DATA
+ };
+
+ // internal parsing helpers
+ const char *infoname() const { return m_current_info ? m_current_info->shortname().c_str() : "???"; }
+ int line() const { return XML_GetCurrentLineNumber(m_parser); }
+ int column() const { return XML_GetCurrentColumnNumber(m_parser); }
+ const char *parser_error() const { return XML_ErrorString(XML_GetErrorCode(m_parser)); }
+
+ // internal error helpers
+ template <typename Format, typename... Params> void parse_error(Format &&fmt, Params &&... args);
+ void unknown_tag(const char *tagname) { parse_error("Unknown tag: %s", tagname); }
+ void unknown_attribute(const char *attrname) { parse_error("Unknown attribute: %s", attrname); }
+
+ // internal helpers
+ template <size_t N> std::array<std::string_view, N> parse_attributes(const char **attributes, const char *const (&attrlist)[N]);
+ bool parse_name_and_value(const char **attributes, std::string_view &name, std::string_view &value);
+ void add_rom_entry(std::string &&name, std::string &&hashdata, u32 offset, u32 length, u32 flags);
+
+ // expat callbacks
+ static void start_handler(void *data, const char *tagname, const char **attributes);
+ static void data_handler(void *data, const char *s, int len);
+ static void end_handler(void *data, const char *name);
+
+ // internal parsing
+ void parse_root_start(const char *tagname, const char **attributes);
+ void parse_main_start(const char *tagname, const char **attributes);
+ void parse_soft_start(const char *tagname, const char **attributes);
+ void parse_part_start(const char *tagname, const char **attributes);
+ void parse_data_start(const char *tagname, const char **attributes);
+ void parse_main_end(const char *tagname);
+ void parse_soft_end(const char *name);
+
+ // internal parsing state
+ const std::string_view m_filename;
+ std::list<software_info> & m_infolist;
+ std::ostream & m_errors;
+ struct XML_ParserStruct * m_parser;
+ std::string & m_listname;
+ std::string & m_description;
+ bool m_data_accum_expected;
+ bool m_ignore_cdata;
+ std::string m_data_accum;
+ software_info * m_current_info;
+ software_part * m_current_part;
+ parse_position m_pos;
+};
+
+
//-------------------------------------------------
// softlist_parser - constructor
//-------------------------------------------------
-softlist_parser::softlist_parser(util::core_file &file, const std::string &filename, std::string &description, std::list<software_info> &infolist, std::ostringstream &errors) :
- m_file(file),
+softlist_parser::softlist_parser(
+ util::read_stream &file,
+ std::string_view filename,
+ std::string &listname,
+ std::string &description,
+ std::list<software_info> &infolist,
+ std::ostream &errors) :
m_filename(filename),
m_infolist(infolist),
m_errors(errors),
- m_done(false),
+ m_listname(listname),
m_description(description),
m_data_accum_expected(false),
+ m_ignore_cdata(false),
m_current_info(nullptr),
m_current_part(nullptr),
m_pos(POS_ROOT)
{
// create the parser
m_parser = XML_ParserCreate_MM(nullptr, nullptr, nullptr);
- if (m_parser == nullptr)
+ if (!m_parser)
throw std::bad_alloc();
// set the handlers
@@ -215,13 +283,13 @@ softlist_parser::softlist_parser(util::core_file &file, const std::string &filen
XML_SetCharacterDataHandler(m_parser, &softlist_parser::data_handler);
// parse the file contents
- m_file.seek(0, SEEK_SET);
char buffer[1024];
- while (!m_done)
+ for (bool done = false; !done; )
{
- u32 length = m_file.read(buffer, sizeof(buffer));
- m_done = m_file.eof();
- if (XML_Parse(m_parser, buffer, length, m_done) == XML_STATUS_ERROR)
+ auto const [err, length] = read(file, buffer, sizeof(buffer)); // TODO: better error handling
+ if (!length)
+ done = true;
+ if (XML_Parse(m_parser, buffer, length, done) == XML_STATUS_ERROR)
{
parse_error("%s", parser_error());
break;
@@ -234,36 +302,6 @@ softlist_parser::softlist_parser(util::core_file &file, const std::string &filen
//-------------------------------------------------
-// line
-//-------------------------------------------------
-
-int softlist_parser::line() const
-{
- return XML_GetCurrentLineNumber(m_parser);
-}
-
-
-//-------------------------------------------------
-// column
-//-------------------------------------------------
-
-int softlist_parser::column() const
-{
- return XML_GetCurrentColumnNumber(m_parser);
-}
-
-
-//-------------------------------------------------
-// parser_error
-//-------------------------------------------------
-
-const char *softlist_parser::parser_error() const
-{
- return XML_ErrorString(XML_GetErrorCode(m_parser));
-}
-
-
-//-------------------------------------------------
// parse_error - append a parsing error with
// filename, line and column information
//-------------------------------------------------
@@ -287,10 +325,10 @@ inline void softlist_parser::parse_error(Format &&fmt, Params &&... args)
// attributes into a list of strings
//-------------------------------------------------
-template <typename T>
-std::vector<std::string> softlist_parser::parse_attributes(const char **attributes, const T &attrlist)
+template <size_t N>
+std::array<std::string_view, N> softlist_parser::parse_attributes(const char **attributes, const char *const (&attrlist)[N])
{
- std::vector<std::string> outlist(std::distance(std::begin(attrlist), std::end(attrlist)));
+ std::array<std::string_view, N> outlist;
// iterate over attribute/value pairs
for( ; attributes[0]; attributes += 2)
@@ -323,7 +361,7 @@ std::vector<std::string> softlist_parser::parse_attributes(const char **attribut
// latter to be defined as an empty string)
//-------------------------------------------------
-bool softlist_parser::parse_name_and_value(const char **attributes, std::string &name, std::string &value)
+bool softlist_parser::parse_name_and_value(const char **attributes, std::string_view &name, std::string_view &value)
{
bool found_value = false;
@@ -430,6 +468,7 @@ void softlist_parser::end_handler(void *data, const char *name)
break;
case POS_MAIN:
+ state->parse_main_end(name);
state->m_current_info = nullptr;
break;
@@ -447,6 +486,7 @@ void softlist_parser::end_handler(void *data, const char *name)
// stop accumulating
state->m_data_accum_expected = false;
+ state->m_ignore_cdata = false;
state->m_data_accum.clear();
}
@@ -459,18 +499,25 @@ void softlist_parser::data_handler(void *data, const char *s, int len)
{
softlist_parser *state = reinterpret_cast<softlist_parser *>(data);
- // if we have an std::string to accumulate data in, do it
- if (state->m_data_accum_expected)
+ if (state->m_ignore_cdata)
+ {
+ // allowed, but we don't use it
+ }
+ else if (state->m_data_accum_expected)
+ {
+ // if we have an std::string to accumulate data in, do it
state->m_data_accum.append(s, len);
-
- // otherwise, report an error if the data is non-blank
+ }
else
+ {
+ // otherwise, report an error if the data is non-blank
for (int i = 0; i < len; i++)
if (!isspace(s[i]))
{
state->parse_error("Unexpected content");
break;
}
+ }
}
@@ -486,6 +533,9 @@ void softlist_parser::parse_root_start(const char *tagname, const char **attribu
static char const *const attrnames[] = { "name", "description" };
const auto attrvalues = parse_attributes(attributes, attrnames);
+ if (!attrvalues[0].empty())
+ m_listname = attrvalues[0];
+
if (!attrvalues[1].empty())
m_description = attrvalues[1];
}
@@ -501,27 +551,37 @@ void softlist_parser::parse_root_start(const char *tagname, const char **attribu
void softlist_parser::parse_main_start(const char *tagname, const char **attributes)
{
- // <software name='' cloneof='' supported=''>
if (strcmp(tagname, "software") == 0)
{
+ // <software name='' cloneof='' supported=''>
static char const *const attrnames[] = { "name", "cloneof", "supported" };
auto attrvalues = parse_attributes(attributes, attrnames);
if (!attrvalues[0].empty())
{
- m_infolist.emplace_back(std::move(attrvalues[0]), std::move(attrvalues[1]), attrvalues[2].c_str());
+ m_infolist.emplace_back(std::string(attrvalues[0]), std::string(attrvalues[1]), attrvalues[2]);
m_current_info = &m_infolist.back();
}
else
parse_error("No name defined for item");
}
+ else if (strcmp(tagname, "notes") == 0)
+ {
+ // <notes>
+ m_ignore_cdata = true;
+ }
else
unknown_tag(tagname);
}
+void softlist_parser::parse_main_end(const char *tagname)
+{
+}
+
+
//-------------------------------------------------
-// parse_main_start - handle tag start within
+// parse_soft_start - handle tag start within
// a software tag
//-------------------------------------------------
@@ -546,13 +606,17 @@ void softlist_parser::parse_soft_start(const char *tagname, const char **attribu
else if (strcmp(tagname, "publisher") == 0)
m_data_accum_expected = true;
+ // <notes>
+ else if (strcmp(tagname, "notes") == 0)
+ m_ignore_cdata = true;
+
// <info name='' value=''>
else if (strcmp(tagname, "info") == 0)
{
- std::string infoname, infovalue;
+ std::string_view infoname, infovalue;
if (parse_name_and_value(attributes, infoname, infovalue))
- m_current_info->m_other_info.emplace_back(std::move(infoname), std::move(infovalue));
+ m_current_info->m_info.emplace_back(std::string(infoname), std::string(infovalue));
else
parse_error("Incomplete other_info definition");
}
@@ -560,12 +624,17 @@ void softlist_parser::parse_soft_start(const char *tagname, const char **attribu
// <sharedfeat name='' value=''>
else if (strcmp(tagname, "sharedfeat") == 0)
{
- std::string featname, featvalue;
+ std::string_view featname, featvalue;
if (parse_name_and_value(attributes, featname, featvalue))
- m_current_info->m_shared_info.emplace_back(std::move(featname), std::move(featvalue));
+ {
+ if (!m_current_info->m_shared_features.emplace(std::string(featname), std::string(featvalue)).second)
+ parse_error("Duplicate sharedfeat name");
+ }
else
+ {
parse_error("Incomplete sharedfeat definition");
+ }
}
// <part name='' interface=''>
@@ -576,7 +645,7 @@ void softlist_parser::parse_soft_start(const char *tagname, const char **attribu
if (!attrvalues[0].empty() && !attrvalues[1].empty())
{
- m_current_info->m_partdata.emplace_back(*m_current_info, std::move(attrvalues[0]), std::move(attrvalues[1]));
+ m_current_info->m_partdata.emplace_back(*m_current_info, std::string(attrvalues[0]), std::string(attrvalues[1]));
m_current_part = &m_current_info->m_partdata.back();
}
else
@@ -610,8 +679,8 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu
if (!attrvalues[0].empty() && !attrvalues[1].empty())
{
// handle region attributes
- const std::string &width = attrvalues[2];
- const std::string &endianness = attrvalues[3];
+ const auto &width = attrvalues[2];
+ const auto &endianness = attrvalues[3];
u32 regionflags = ROMENTRYTYPE_REGION;
if (!width.empty())
@@ -637,7 +706,7 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu
parse_error("Invalid dataarea endianness");
}
- add_rom_entry(std::move(attrvalues[0]), "", 0, strtol(attrvalues[1].c_str(), nullptr, 0), regionflags);
+ add_rom_entry(std::string(attrvalues[0]), "", 0, strtol(attrvalues[1].data(), nullptr, 0), regionflags);
}
else
parse_error("Incomplete dataarea definition");
@@ -650,7 +719,7 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu
auto attrvalues = parse_attributes(attributes, attrnames);
if (!attrvalues[0].empty())
- add_rom_entry(std::move(attrvalues[0]), "", 0, 1, ROMENTRYTYPE_REGION | ROMREGION_DATATYPEDISK);
+ add_rom_entry(std::string(attrvalues[0]), "", 0, 1, ROMENTRYTYPE_REGION | ROMREGION_DATATYPEDISK);
else
parse_error("Incomplete diskarea definition");
}
@@ -658,12 +727,17 @@ void softlist_parser::parse_part_start(const char *tagname, const char **attribu
// <feature name='' value=''>
else if (strcmp(tagname, "feature") == 0)
{
- std::string featname, featvalue;
+ std::string_view featname, featvalue;
if (parse_name_and_value(attributes, featname, featvalue))
- m_current_part->m_featurelist.emplace_back(std::move(featname), std::move(featvalue));
+ {
+ if (!m_current_part->m_features.emplace(std::string(featname), std::string(featvalue)).second)
+ parse_error("Duplicate feature name");
+ }
else
+ {
parse_error("Incomplete feature definition");
+ }
}
// <dipswitch>
@@ -694,18 +768,18 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu
static char const *const attrnames[] = { "name", "size", "crc", "sha1", "offset", "value", "status", "loadflag" };
auto attrvalues = parse_attributes(attributes, attrnames);
- std::string &name = attrvalues[0];
- const std::string &sizestr = attrvalues[1];
- const std::string &crc = attrvalues[2];
- const std::string &sha1 = attrvalues[3];
- const std::string &offsetstr = attrvalues[4];
- std::string &value = attrvalues[5];
- const std::string &status = attrvalues[6];
- const std::string &loadflag = attrvalues[7];
+ const std::string_view &name = attrvalues[0];
+ const std::string_view &sizestr = attrvalues[1];
+ const std::string_view &crc = attrvalues[2];
+ const std::string_view &sha1 = attrvalues[3];
+ const std::string_view &offsetstr = attrvalues[4];
+ const std::string_view &value = attrvalues[5];
+ const std::string_view &status = attrvalues[6];
+ const std::string_view &loadflag = attrvalues[7];
if (!sizestr.empty())
{
- u32 length = strtol(sizestr.c_str(), nullptr, 0);
- u32 offset = offsetstr.empty() ? 0 : strtol(offsetstr.c_str(), nullptr, 0);
+ u32 length = strtol(sizestr.data(), nullptr, 0);
+ u32 offset = offsetstr.empty() ? 0 : strtol(offsetstr.data(), nullptr, 0);
if (loadflag == "reload")
add_rom_entry("", "", offset, length, ROMENTRYTYPE_RELOAD | ROM_INHERITFLAGS);
@@ -714,7 +788,7 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu
else if (loadflag == "continue")
add_rom_entry("", "", offset, length, ROMENTRYTYPE_CONTINUE | ROM_INHERITFLAGS);
else if (loadflag == "fill")
- add_rom_entry("", std::move(value), offset, length, ROMENTRYTYPE_FILL);
+ add_rom_entry("", std::string(value), offset, length, ROMENTRYTYPE_FILL);
else if (loadflag == "ignore")
add_rom_entry("", "", 0, length, ROMENTRYTYPE_IGNORE | ROM_INHERITFLAGS);
else if (!name.empty())
@@ -750,7 +824,7 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu
else if (loadflag == "load32_byte")
romflags = ROM_SKIP(3);
- add_rom_entry(std::move(name), std::move(hashdata), offset, length, ROMENTRYTYPE_ROM | romflags);
+ add_rom_entry(std::string(name), std::move(hashdata), offset, length, ROMENTRYTYPE_ROM | romflags);
}
else
parse_error("Rom name missing");
@@ -765,10 +839,10 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu
static char const *const attrnames[] = { "name", "sha1", "status", "writeable" };
auto attrvalues = parse_attributes(attributes, attrnames);
- std::string &name = attrvalues[0];
- const std::string &sha1 = attrvalues[1];
- const std::string &status = attrvalues[2];
- const std::string &writeablestr = attrvalues[3];
+ const std::string_view &name = attrvalues[0];
+ const std::string_view &sha1 = attrvalues[1];
+ const std::string_view &status = attrvalues[2];
+ const std::string_view &writeablestr = attrvalues[3];
if (!name.empty() && !sha1.empty())
{
const bool baddump = (status == "baddump");
@@ -776,7 +850,7 @@ void softlist_parser::parse_data_start(const char *tagname, const char **attribu
const bool writeable = (writeablestr == "yes");
std::string hashdata = string_format("%c%s%s", util::hash_collection::HASH_SHA1, sha1, (nodump ? NO_DUMP : (baddump ? BAD_DUMP : "")));
- add_rom_entry(std::move(name), std::move(hashdata), 0, 0, ROMENTRYTYPE_ROM | (writeable ? DISK_READWRITE : DISK_READONLY));
+ add_rom_entry(std::string(name), std::move(hashdata), 0, 0, ROMENTRYTYPE_ROM | (writeable ? DISK_READWRITE : DISK_READONLY));
}
else if (status != "nodump") // a no_dump chd is not an incomplete entry
parse_error("Incomplete disk definition");
@@ -801,15 +875,15 @@ void softlist_parser::parse_soft_end(const char *tagname)
// <description>
if (strcmp(tagname, "description") == 0)
- m_current_info->m_longname = m_data_accum;
+ m_current_info->m_longname = std::move(m_data_accum);
// <year>
else if (strcmp(tagname, "year") == 0)
- m_current_info->m_year = m_data_accum;
+ m_current_info->m_year = std::move(m_data_accum);
// <publisher>
else if (strcmp(tagname, "publisher") == 0)
- m_current_info->m_publisher = m_data_accum;
+ m_current_info->m_publisher = std::move(m_data_accum);
// </part>
else if (strcmp(tagname, "part") == 0)
@@ -823,14 +897,27 @@ void softlist_parser::parse_soft_end(const char *tagname)
if (!m_current_part->m_romdata.empty())
add_rom_entry("", "", 0, 0, ROMENTRYTYPE_END);
- // get the info; if present, copy shared data (we assume name/value strings live
- // in the string pool and don't need to be reallocated)
+ // get the info; if present, copy shared data
if (m_current_info != nullptr)
- for (const feature_list_item &item : m_current_info->shared_info())
- m_current_part->m_featurelist.emplace_back(item.name(), item.value());
+ for (const software_info_item &item : m_current_info->shared_features())
+ m_current_part->m_features.emplace(item.name(), item.value());
}
}
+} // namespace detail
+
+
+void parse_software_list(
+ util::read_stream &file,
+ std::string_view filename,
+ std::string &listname,
+ std::string &description,
+ std::list<software_info> &infolist,
+ std::ostream &errors)
+{
+ detail::softlist_parser(file, filename, listname, description, infolist, errors);
+}
+
//-------------------------------------------------
// software_name_parse - helper that splits a
@@ -848,10 +935,10 @@ void softlist_parser::parse_soft_end(const char *tagname)
// case.
//-------------------------------------------------
-bool software_name_parse(const std::string &identifier, std::string *list_name, std::string *software_name, std::string *part_name)
+bool software_name_parse(std::string_view identifier, std::string *list_name, std::string *software_name, std::string *part_name)
{
// first, sanity check the arguments
- if (!std::regex_match(identifier, s_potenial_softlist_regex))
+ if (!std::regex_match(identifier.begin(), identifier.end(), s_potential_softlist_regex))
return false;
// reset all output parameters (if specified of course)
@@ -864,7 +951,7 @@ bool software_name_parse(const std::string &identifier, std::string *list_name,
// if no colon, this is the swname by itself
auto split1 = identifier.find_first_of(':');
- if (split1 == std::string::npos)
+ if (split1 == std::string_view::npos)
{
if (software_name != nullptr)
*software_name = identifier;
@@ -873,7 +960,7 @@ bool software_name_parse(const std::string &identifier, std::string *list_name,
// if one colon, it is the swname and swpart alone
auto split2 = identifier.find_first_of(':', split1 + 1);
- if (split2 == std::string::npos)
+ if (split2 == std::string_view::npos)
{
if (software_name != nullptr)
*software_name = identifier.substr(0, split1);