/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the MsXpertSuite project.
 *
 * The MsXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// Local includes
#include "globals.hpp"
#include "Modif.hpp"
#include "PolChemDef.hpp"
#include "Monomer.hpp"


namespace MsXpS
{

namespace libXpertMass
{


/*!
\class MsXpS::libXpertMass::Modif
\inmodule libXpertMass
\ingroup PolChemDefBuildingdBlocks
\inheaderfile Modif.hpp

\brief The Modif class provides abstractions to work with
chemical modifications.

The Modif class provides a chemical modification that can be set to any monomer
in a polymer sequence or to any one of the polymer sequence ends.In the protein
world, chemical modifications of proteins that occur in the living cell are
called post-translational modifications.This class aims at modelling, among
others, such modifications.

  The chemical reaction described by the Modif class is encoded as an
actionformula (see \l{Formula}).
   */

/*!
   \variable int MsXpS::libXpertMass::Modif::m_targets

   \brief String that holds a list of all the target monomers of this
modification.

  If there are more than one target, the targets (monomer codes)
must be separated by ';' characters.

  If any monomer in the polymer chemistry definition might be modified by this
Modif object, then, the "*" string can be used to indicate so.
 */

/*!
   \variable int MsXpS::libXpertMass::Modif::m_maxCount

   \brief Value indicating the maximum number of times this modification
can be set to a target entity (monomer or polymer).
 */


/*!
  \brief Constructs a modification.

  A Modif instance cannot be of any use if it is not associated logically to a
polymer chemistry definition (\a pol_chem_def_csp). The formula is defined by
its \a name and its \a formula.

  The \a formula might be a simple formula ("O", for an oxydation) or an
actionformula, if that is required to best characterize the modification
("-H20+CH3COOH", for example, for an acetylation).

  The Ponderable base class' mono and avg masses are initialized to (0,0).

  The targets (m_targets) is set to "all", that is \c "*" and the maximum count
of this modification that can set set to a target is set to \c 1 by default.
  */
Modif::Modif(PolChemDefCstSPtr pol_chem_def_csp, QString name, QString formula)
  : PolChemDefEntity(pol_chem_def_csp, name),
    Formula(formula),
    Ponderable(0, 0),
    m_targets("*"),
    m_maxCount(1)
{
  // By default a modification will target any monomer.  By default
  // the modification will be settable only once at maximum to a
  // given monomer entity.
}


/*!
  \brief Constructs a Modif object as a copy of \a other.
 */
Modif::Modif(const Modif &other)
  : PolChemDefEntity(other),
    Formula(other),
    Ponderable(other),
    PropListHolder(other),
    m_targets(other.m_targets),
    m_maxCount(other.m_maxCount)
{
}

/*!
  \brief Destructs this Modif.
 */
Modif::~Modif()
{
}


/*!
  \brief Assigns \a other to this modification.

  Returns a reference to this modification.
  */
Modif &
Modif::operator=(const Modif &other)
{
  if(&other == this)
    return *this;

  PolChemDefEntity::operator=(other);
  Formula::operator=(other);
  Ponderable::operator=(other);
  PropListHolder::operator=(other);

  return *this;
}


/*! Resets this modification to an empty object.
 */
void
Modif::reset()
{
  m_name = "NOT_SET";

  m_formula.clear();
  m_plusFormula.clear();
  m_minusFormula.clear();

  // When m_targets is empty, the modification cannot modify
  // anything. Useful for testing purposes.
  m_targets.clear();
  m_maxCount = 1;

  m_mono = 0;
  m_avg  = 0;

  while(!m_propList.isEmpty())
    delete m_propList.takeFirst();
}

/*!
  \brief Sets the \a targets for this modification.

  Setting \e{targets} means specifying which target might be modified using
this modification. For example, for \c Phosphorylation, in protein chemistry,
one would define targets as \c Serine, \c Threonine, \c Tyrosine (there are
other targets, but very rarely encountered).

  Multiple targets are separated using ';'.

*/
QString &
Modif::setTargets(QString targets)
{
  if(targets.isEmpty())
    {
      m_targets.clear();
      return m_targets;
    }

  //   qDebug() << __FILE__ << __LINE__
  // 	    << "Before unspacification" << targets;

  // Remove any space from 'targets'.
  m_targets = unspacifyString(targets);

  //   qDebug() << __FILE__ << __LINE__
  // 	    << "After unspacification" << m_targets;

  // Validate and simplify: true is by default the bool param.
  if(!validateTargets())
    m_targets = QString();

  return m_targets;
}

/*!
  \brief Returns the tagets of this modification.
*/
QString
Modif::targets() const
{
  return m_targets;
}

/*!
  \brief Returns the tagets of this modification in the form a string list.

  The member m_targets string is split using ';' as a delimitor, the
obtained list of strings is set to \a string_list and the size of the list is
returned.
*/
int
Modif::targets(QStringList &string_list) const
{
  // Return a string list after splitting at ';'.

  string_list.clear();

  string_list = m_targets.split(';', Qt::SkipEmptyParts, Qt::CaseSensitive);
  return string_list.size();
}

/*!
  \brief Returns true if monomer \a code is found among the targets of this
modifications, false otherwise.
*/
bool
Modif::hasMonomerTarget(QString code) const
{
  if(m_targets == "*")
    return true;

  if(m_targets == "!")
    return false;

  QString delimitedCode = QString(";%1;").arg(code);

  // The m_targets string is in the form ";code;code;code;".

  return m_targets.contains(delimitedCode, Qt::CaseSensitive);
}

/*!
  \brief Validates the target of this modification.

  The target list is split using ';' as a delimiter. If '*' is found and \a
simplify is true, the function returns true immediately (as all the
monomers in the polymer chemistry definition might be a target of this
modification).

  If at least one target is found, then each target is a monomer
code and that code is looked for in the member polymer chemistry definition list
of monomers. If the code is found, that code is added to a temporary string. If
the code is not found, m_targets is set to that temporary string (only having in
it monomer codes found in the polymer chemistry definition's list of monomers)
and the function returns false.

  Returns true if the targets of this modification all validated successfully.
*/
bool
Modif::validateTargets(bool simplify)
{
  // A targets string cannot contain both a '*' and a '!'
  // character. We check that immediately.
  if(m_targets.contains('*') && m_targets.contains('!'))
    return false;

  QList<Monomer *> monomerList = mcsp_polChemDef->monomerList();

  QString result;

  // If the m_targets is empty, this is an error, because we cannot
  // know what's to be done with the modification.

  if(m_targets.isEmpty())
    {
      return false;
    }

  // A targets string looks like "Ser ; Thr ; Tyr".
  QStringList stringList =
    m_targets.split(';', Qt::SkipEmptyParts, Qt::CaseSensitive);

  for(int iter = 0; iter < stringList.size(); ++iter)
    {
      QString currentString = stringList.at(iter);

      // There are two character that might be encountered: '*' is the
      // equivalent of "all the monomers in the definition"; '!' is
      // equivalent to "none of the monomers in the definition". But
      // it is not possible that both * and ! be present in the same
      // targets string.

      if(currentString == "*" || currentString == "!")
        {
          // Simplification asked: if '*' is found then it can be
          // there alone. Same for '!'. '*' means that any monomer in
          // the definition might be modified with this modification,
          // '!' means that none of the monomers might be modified.

          if(simplify)
            {
              m_targets = currentString;

              return true;
            }
          else
            {
              result.append(QString("%1;").arg(currentString));
            }

          continue;
        }

      // At this point, we have something to check as a monomer code:

      if(Monomer::isCodeInList(currentString, monomerList) == -1)
        {
          qDebug() << "Monomer code is not known:" << currentString;

          m_targets = result;

          return false;
        }
      else
        {
          // Want the string to be ;code;code;code;(notice the first
          // and last ';'), so that we can later ask easily if ;Lys;
          // is found in the targets string, without risking to also
          // match ;Ly;.
          if(!result.size())
            result.append(QString(";%1;").arg(currentString));
          else
            result.append(QString("%1;").arg(currentString));
        }
    }

  if(result.isEmpty())
    return false;

  m_targets = result;

  return true;
}


/*!
  \brief Set the maximum count of times that this modification might be set to
a target to \a value.
*/
void
Modif::setMaxCount(int value)
{
  Q_ASSERT(value > 0);

  m_maxCount = value;
}

/*!
  \brief Returns the maximum count of times that this modification might be
set to a target.
*/
int
Modif::maxCount()
{
  return m_maxCount;
}

/*
  \brief Returns the formula describing this modification.
  */
QString
Modif::formula() const
{
  return Formula::toString();
}


/*!
  \brief Returns true if this and the \a other modifications are identical,
false otherwise.
*/
bool
Modif::operator==(const Modif &other)
{
  int tests = 0;

  tests += (m_targets == other.m_targets);
  tests += PolChemDefEntity::operator==(other);
  tests += Formula::operator==(other);
  tests += Ponderable::operator==(other);

  if(tests < 4)
    return false;

  return true;
}

/*!
  \brief Returns true if this and the \a other modifications differ,
false otherwise.
*/
bool
Modif::operator!=(const Modif &other)
{
  int tests = 0;

  tests += (m_targets != other.m_targets);
  tests += PolChemDefEntity::operator!=(other);
  tests += Formula::operator!=(other);
  tests += Ponderable::operator!=(other);

  if(tests > 0)
    return true;

  return false;
}

/*!
  \brief Returns true if this modification's name is found in the member
polymer chemistry definition's list of modifications, false otherwise
*/
int
Modif::isNameKnown()
{
  const QList<Modif *> &refList = mcsp_polChemDef->modifList();

  if(m_name.isEmpty())
    return -1;

  for(int iter = 0; iter < refList.size(); ++iter)
    {
      if(refList.at(iter)->m_name == m_name)
        return iter;
    }

  return -1;
}


/*!
  \brief Searches for a modification by name \a name in the reference list \a
refList.

  If a modification is found, and \a other is non-nullptr, the found modif is
copied to \a other.

  Returns true if the modification was found, false otherwise.
  */
int
Modif::isNameInList(const QString &name,
                    const QList<Modif *> &refList,
                    Modif *other)
{
  Modif *iter_modif_p = 0;

  if(name.isEmpty())
    return -1;

  // qDebug() << "Looking for modif by name" << name;

  for(int iter = 0; iter < refList.size(); ++iter)
    {
      iter_modif_p = refList.at(iter);
      Q_ASSERT(iter_modif_p);

      if(iter_modif_p->m_name == name)
        {
          Modif test_modif(*iter_modif_p);

          if(other)
            {
              *other = *iter_modif_p;
            }
          return iter;
        }
    }

  return -1;
}


/*!
  \brief Validates this modification.

  The modification validates successfully if:

  \list
  \li The member polymer chemistry definition must be set
  \li The name is not empty
  \li The formula validates successfully
  \li The targets validate successfully
  \li The m_maxCount member is greater than 0
  \endlist

  Returns true if the modification validates successfully, false otherwise.
*/
bool
Modif::validate()
{
  if(mcsp_polChemDef == nullptr)
    return false;

  if(m_name.isEmpty())
    return false;

  Formula formula(m_formula);

  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  if(!formula.validate(isotopic_data_csp))
    {
      qDebug() << "The formula failed to validate.";

      return false;
    }

  if(!validateTargets())
    {
      qDebug() << "The targets failed to validate.";
      return false;
    }

  if(m_maxCount <= 0)
    return false;

  return true;
}


/*!
  \brief Calculates the net masses of this modification.

  The masses of the modification are the masses (monoisotopic and average) that
are added to the target as a result of that target being modified with this
modification.

  The calculated masses are set to the Ponderable base class' m_mono and m_avg
members.

  Returns true if the mass calculations were successful, false otherwise.

  \sa Formula::accountMasses()
  */
bool
Modif::calculateMasses()
{
  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  m_mono = 0;
  m_avg  = 0;

  qDebug() << "Right before accounting modif formula masses for formula:"
           << m_formula;

  if(!Formula::accountMasses(isotopic_data_csp, &m_mono, &m_avg))
    {
      qDebug() << "Failed accounting masses for modif:" << m_name
               << "and formula:" << m_formula;
      return false;
    }

  qDebug() << "Right after accounting modif formula masses for formula:"
           << m_formula << m_mono << "-" << m_avg;

  return true;
}


/*!
  \brief Adds to \a mono and \a avg the corresponding mass of this
modification.

  The m_mono and m_avg masses are added to the arguments. The masses are
compounded by factor \a times before the addition.

  Returns true.
*/
bool
Modif::accountMasses(double *mono, double *avg, int times)
{
  if(mono)
    *mono += m_mono * times;

  if(avg)
    *avg += m_avg * times;

  return true;
}


/*!
  \brief Parses the modification XML \a element specifically for \a version.

  Parses the modif \c mdf XML element passed as argument and for each
  encountered data will set the data to this modif (this is
  called XML rendering).The parsing is delegated to a function that is specific
for \a version of the polymer chemistry definition.

  The \c mdf XML element is found in the polymer chemistry definition and has
the following form:


  \code
      <mdf>
        <name>Acetylation</name>
        <formula>C2H2O1</formula>
        <targets>;K;</targets>
        <maxcount>1</maxcount>
      </mdf>
      <mdf>
        <name>AmidationAsp</name>
        <formula>H1N1-O1</formula>
        <targets>;D;</targets>
        <maxcount>1</maxcount>
      </mdf>
  \endcode

  After setting all the data, this modification calculates it masses and
  validates itself. If any of these steps fails, the error is reported
  by returning false.

  Returns true if parsing was successful, false otherwise.
  */
bool
Modif::renderXmlMdfElement(const QDomElement &element, int version)
{
  if(element.tagName() != "mdf")
    return false;

  if(version == 1)
    {
      // no-op
      version = 1;
    }

  QDomElement child;

  if(element.tagName() != "mdf")
    return false;

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();
  if(m_name.isEmpty())
    return false;

  child = child.nextSiblingElement("formula");

  if(child.isNull())
    return false;

  if(!Formula::renderXmlFormulaElement(child))
    return false;

  if(!calculateMasses())
    {
      qDebug() << "Failed accounting masses for modif: " << m_name
               << "with formula:" << m_formula;
      return false;
    }

  child = child.nextSiblingElement("targets");

  if(child.isNull())
    return false;

  m_targets = child.text();

  child = child.nextSiblingElement("maxcount");

  if(child.isNull())
    return false;

  bool ok = false;

  m_maxCount = child.text().toInt(&ok);

  if(!m_maxCount && !ok)
    return false;

  // The validation will take care of checking that the <targets>
  // element did have correct text inside and that <maxcount> be
  // correct also.

  if(!validate())
    return false;

  return true;
}


/*!
  \brief Formats this modification's data as a string suitable to be used as a
\c mdf XML element in the polymer chemistry definition.

  The typical modification element that is generated in this function looks
like this:

  \code
      <mdf>
        <name>Acetylation</name>
        <formula>C2H2O1</formula>
        <targets>;K;</targets>
        <maxcount>1</maxcount>
      </mdf>
      <mdf>
        <name>AmidationAsp</name>
        <formula>H1N1-O1</formula>
        <targets>;D;</targets>
        <maxcount>1</maxcount>
      </mdf>
  \endcode

  The formatting of the XML element takes into account \a offset and \a
indent by prepending the string with \a offset * \a indent character substring.

  \a indent defaults to two spaces.

  Returns a dynamically allocated string that needs to be freed after
  use.
  */
QString *
Modif::formatXmlMdfElement(int offset, const QString &indent)
{
  int newOffset;
  int iter = 0;

  QString lead("");
  QString *string = new QString();

  // Prepare the lead.
  newOffset = offset;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  /* We are willing to create an <modif> node that should look like this:
   *
   <mdf>
   <name>Phosphorylation</name>
   <formula>-H+H2PO3</formula>
   <targets>S;T;Y</targets>
   <maxcount>1</maxcount>
   </mdf>
   *
   */

  *string += QString("%1<mdf>\n").arg(lead);

  // Prepare the lead.
  ++newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  // Continue with indented elements.

  *string += QString("%1<name>%2</name>\n").arg(lead).arg(m_name);

  *string += QString("%1<formula>%2</formula>\n").arg(lead).arg(m_formula);

  *string += QString("%1<targets>%2</targets>\n").arg(lead).arg(m_targets);

  *string += QString("%1<maxcount>%2</maxcount>\n").arg(lead).arg(m_maxCount);

  // Prepare the lead for the closing element.
  --newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  *string += QString("%1</mdf>\n").arg(lead);

  return string;
}


/*!
  \brief Outputs a string representing this modification using qDebug().
*/
void
Modif::debugPutStdErr()
{
  qDebug() << m_name << m_formula << m_targets;
}


//////////////////////// ModifProp ////////////////////////
//////////////////////// ModifProp ////////////////////////


/*!
\class MsXpS::libXpertMass::ModifProp
\inmodule libXpertMass
\ingroup ThePropSystem

\brief The ModifProp class provides a Prop instance of which the member data
points to a dynamically allocated \l Modif instance.

The member datum \l m_name is set to "MODIF".
*/

/*!
\brief Constructs a ModifProp instance using \a modif.

The \a modif pointer is set to the \l mpa_data member.
*/
ModifProp::ModifProp(Modif *modif) : Prop("MODIF")
{
  mpa_data = static_cast<void *>(modif);
}


/*!
\brief Constructs a ModifProp instance as a copy of \a other.
*/
ModifProp::ModifProp(const ModifProp &other) : Prop(other)
{
  if(other.mpa_data != nullptr)
    {
      Modif *modif = static_cast<Modif *>(other.mpa_data);

      mpa_data = static_cast<void *>(new Modif(*modif));
    }
  else
    mpa_data = nullptr;
}

/*!
\brief Destructs this ModifProp instance.

The deletion of the data are delegated to \l deleteData().
*/
ModifProp::~ModifProp()
{
  deleteData();
}

/*!
\brief Deletes the member data.
*/
void
ModifProp::deleteData()
{
  if(mpa_data != nullptr)
    {
      delete static_cast<Modif *>(mpa_data);
      mpa_data = nullptr;
    }
}

/*!
\brief Assigns \a other to this ModifProp instance.

The member data are first deleted and then set to a copy of those in \a other.
*/
ModifProp &
ModifProp::operator=(const ModifProp &other)
{
  if(&other == this)
    return *this;

  Prop::operator=(other);

  if(mpa_data != nullptr)
    deleteData();

  if(other.mpa_data)
    {
      Modif *modif = static_cast<Modif *>(other.mpa_data);

      mpa_data = static_cast<void *>(new Modif(*modif));
    }
  else
    mpa_data = nullptr;

  return *this;
}

/*!
\brief Duplicates this ModifProp instance and returns its pointer.
*/
ModifProp *
ModifProp::cloneOut() const
{
  ModifProp *new_p = new ModifProp(*this);

  return new_p;
}


/*!
\brief Parses the property XML \a element using a \a{version}ed
function.

The element looks like this:

\code
<prop>
  <name>MODIF</name>
  <data>Phosphorylation</data> // That is the Modif name
  <data>-H+H2PO3</data>        // -------------------- formula
  <data>S;T;Y</data>           // -------------------- targets
</prop>
\endcode

As the data in \a element are parsed they are set to the member data, thus
essentially initializing the Modif object pointed to by the member data.

Returns true if parsing was successful, false otherwise.
*/
bool
ModifProp::renderXmlElement(const QDomElement &element, int version)
{
  if(element.tagName() != "prop")
    return false;

  if(version == 1)
    {
      // no-op
      version = 1;
    }

  QDomElement child;

  // The element looks like this:
  //
  // <prop>
  //   <name>MODIF</name>
  //   <data>Phosphorylation</data> // That is the Modif name
  //   <data>-H+H2PO3</data>        // -------------------- formula
  //   <data>S;T;Y</data>           // -------------------- targets
  // </prop>


  if(element.tagName() != "prop")
    return false;

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();

  if(m_name != "MODIF")
    return false;

  // And now we have to manage the prop's data elements. When this
  // ModifProp object was allocated, one Modif was allocated and
  // set as the data of the Prop. Get to it.

  Modif *modif = static_cast<Modif *>(mpa_data);

  // Next sibling is the modif's name data.

  child = child.nextSiblingElement("data");

  if(child.isNull())
    return false;

  modif->setName(child.text());

  // Next sibling is the modif's formula data.

  child = child.nextSiblingElement("data");

  if(child.isNull())
    return false;

  modif->setFormula(child.text());

  if(!modif->calculateMasses())
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to calculate masses for modification:"
               << modif->name();
      return false;
    }

  // Next sibling is the modif's targets data.

  child = child.nextSiblingElement("data");

  if(child.isNull())
    return false;

  modif->setTargets(child.text());

  // The validation will take care of checking that the <targets>
  // element did have correct text inside.

  if(!modif->validate())
    return false;

  return true;
}


/*!
\brief Formats a string suitable to use as an XML element.

Formats a string suitable to be used as an XML element in an XML file (a
polymer sequence file, for example). Typical  ModifProp elements that might be
generated in this function look like this:

\code
<prop>
  <name>MODIF</name>
  <data>Phosphorylation</data> // That is the Modif name
  <data>-H+H2PO3</data>        // -------------------- formula
  <data>S;T;Y</data>           // -------------------- targets
</prop>
\endcode

\a offset times the \a indent string must be used as a lead in the
formatting of elements.

Returns a dynamically allocated string that needs to be freed after use.
*/
QString *
ModifProp::formatXmlElement(int offset, const QString &indent)
{
  int newOffset;
  int iter = 0;

  QString lead("");
  QString *string = new QString();

  // Prepare the lead.
  newOffset = offset;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  // The property has its data member that points to a Modif. Thus
  // the formatting of the element should produce something like this:
  //
  // <prop>
  //   <name>MODIF</name>
  //   <data>Phosphorylation</data> // That is the Modif name
  //   <data>-H+H2PO3</data>        // -------------------- formula
  //   <data>S;T;Y</data>           // -------------------- targets
  // </prop>

  *string += QString("%1<prop>\n").arg(lead);

  // Prepare the lead.
  ++newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  // Continue with indented elements.

  *string += QString("%1<name>%2</name>\n").arg(lead).arg(m_name);

  *string += QString("%1<data>%2</data>\n")
               .arg(lead)
               .arg(static_cast<Modif *>(mpa_data)->name());

  *string += QString("%1<data>%2</data>\n")
               .arg(lead)
               .arg(static_cast<Modif *>(mpa_data)->formula());

  *string += QString("%1<data>%2</data>\n")
               .arg(lead)
               .arg(static_cast<Modif *>(mpa_data)->targets());

  // Prepare the lead for the closing element.
  --newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  *string += QString("%1</prop>\n").arg(lead);

  return string;
}

} // namespace libXpertMass

} // namespace MsXpS
