/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright 2009--2026 by 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 "MsXpS/libXpertMassCore/ChemicalGroup.hpp"

namespace MsXpS
{

namespace libXpertMassCore
{


/*!
\class MsXpS::libXpertMassCore::ChemicalGroup
\inmodule libXpertMassCore
\ingroup PolChemDefBuildingdBlocks
\inheaderfile ChemicalGroup.hpp

\brief The ChemicalGroup class provides a model for specifying the
acido-basic behaviour of a chemical group of either a \l Monomer object or of a
\l Modif object.

If the ChemicalGroup does not prove sufficient to characterize precisely the
acido-basic properties of an entity, \l ChemicalGroupRule instances can be
added to that effect.

In an pkaphpidata definition file, the following xml structure
is encountered:

\code
<pkaphpidata>
  <monomers>
    <monomer>
      <code>A</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    <monomer>
      <code>C</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>Lateral SH2</name>
        <pka>8.3</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>never_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    .....
  <modifs>
    <modif>
      <name>Phosphorylation</name>
      <mdfchemgroup>
        <name>none_set</name>
        <pka>1.2</pka>
        <acidcharged>FALSE</acidcharged>
      </mdfchemgroup>
      <mdfchemgroup>
        <name>none_set</name>
        <pka>6.5</pka>
        <acidcharged>FALSE</acidcharged>
      </mdfchemgroup>
    </modif>
  </modifs>
</pkaphpidata>
\endcode

\sa ChemicalGroupRule,
*/

/*!
\variable MsXpS::libXpertMassCore::ChemicalGroup::m_name

\brief The name of the ChemicalGroup instance.
*/

/*!
\variable MsXpS::libXpertMassCore::ChemicalGroup::m_pka

\brief The pKa of the ChemicalGroup instance.
*/

/*!
\variable MsXpS::libXpertMassCore::ChemicalGroup::m_acidCharged

\brief Tells if the group is charged when in acid conditions (that is, the pH
is less than the pKa).
*/

/*!
\variable MsXpS::libXpertMassCore::ChemicalGroup::m_polymerizationRule

\brief The way this ChemicalGroup behaves upon polymerization of the chemical
entity into a \l Polymer.
*/

/*!
\variable MsXpS::libXpertMassCore::ChemicalGroup::m_rules

\brief The container of \l ChemicalGroupRule instances.
*/

/*!
\brief Constructs a ChemicalGroup instance.

\a name The name of this ChemicalGroup.
\a pka The pKa value of this ChemicalGroup.
\a is_acid_charged Tells if the ChemicalGroup bears a charge when in acidic
conditions.
\a polymerization_rule Specifies the polymerization rule.

Upon construction the object is validated and m_isValid is set to the result of
that validation.
*/
ChemicalGroup::ChemicalGroup(const QString &name,
                             float pka,
                             bool is_acid_charged,
                             Enums::ChemicalGroupTrapped polymerization_rule)
  : m_name(name),
    m_pka(pka),
    m_acidCharged(is_acid_charged),
    m_polymerizationRule(polymerization_rule)
{
  if(m_pka <= 0 && m_pka >= 14)
    qFatal() << "Programming error. pKa cannot be <=0 or >= 14.";

  ErrorList error_list;
  m_isValid = validate(&error_list);
  if(!m_isValid)
    qDebug()
      << "The ChemicalGroup right constructed did not validate successfully.";
}

/*!
\brief Construct a ChemicalGroup instance as a copy of \a other.

There is no checking of the validity of \a other.
*/
ChemicalGroup::ChemicalGroup(const ChemicalGroup &other)
  : m_name(other.m_name),
    m_pka(other.m_pka),
    m_acidCharged(other.m_acidCharged),
    m_polymerizationRule(other.m_polymerizationRule),
    m_isValid(other.m_isValid)
{
  //  We want a new allocation, not a shared pointer copy.
  for(const ChemicalGroupRuleSPtr &chemical_group_rule_sp : other.m_rules)
    m_rules.emplace_back(
      std::make_shared<ChemicalGroupRule>(*chemical_group_rule_sp.get()));
}

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

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

\note The ChemicalGroupRule instances in \a other are deep-duplicated.

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

  m_name               = other.m_name;
  m_pka                = other.m_pka;
  m_acidCharged        = other.m_acidCharged;
  m_polymerizationRule = other.m_polymerizationRule;

  //  We want a new allocation, not a shared pointer copy.
  for(const ChemicalGroupRuleSPtr &chemical_group_rule_sp : other.m_rules)
    m_rules.emplace_back(
      std::make_shared<ChemicalGroupRule>(*chemical_group_rule_sp.get()));

  ErrorList error_list;
  m_isValid = validate(&error_list);

  return *this;
}

/*!
\brief Sets the \a name.
*/
void
ChemicalGroup::setName(QString name)
{
  m_name = name;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    {
      qDebug()
        << "After setting name, the object did not validate successfully.";
    }
}

/*!
\brief Returns the name.
*/
QString
ChemicalGroup::getName() const
{
  return m_name;
}

/*!
\brief Sets the pKa to \a pka.
*/
void
ChemicalGroup::setPka(float pka)
{
  m_pka = pka;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    {
      qDebug()
        << "After setting pKa, the object did not validate successfully.";
    }
}

/*!
\brief Returns the pKa.
*/
float
ChemicalGroup::getPka() const
{
  return m_pka;
}

/*!
\brief Sets the charge condition in acidic conditions to \a acid_charged.

If true, the group bears a charge when the pH is less than the pKa.
*/
void
ChemicalGroup::setAcidCharged(bool acid_charged)
{
  m_acidCharged = acid_charged;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    {
      qDebug() << "After setting acid charged bool, the object did not "
                  "validate successfully.";
    }
}

/*!
\brief Returns the charge condition in acidic conditions.

If true, the group bears a charge when the pH is less than the pKa.
*/
bool
ChemicalGroup::isAcidCharged() const
{
  return m_acidCharged;
}

/*!
\brief Sets the polymerization rule to \a pol_rule.

The polymerization rule determines if the chemical group is trapped upon
formation of a Monomer-to-Monomer bond.
*/
void
ChemicalGroup::setPolRule(Enums::ChemicalGroupTrapped pol_rule)
{
  m_polymerizationRule = pol_rule;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    {
      qDebug()
        << "After setting Enums::ChemicalGroupTrapped, the object did not "
           "validate successfully.";
    }
}

/*!
\brief Returns the polymerization rule.
*/
Enums::ChemicalGroupTrapped
ChemicalGroup::getPolRule() const
{
  return m_polymerizationRule;
}

/*!
\brief Returns a const reference to the container of ChemicalGroupRule
instances.
*/
const std::vector<ChemicalGroupRuleSPtr> &
ChemicalGroup::getRulesCstRef() const
{
  return m_rules;
}

/*!
\brief Returns a reference to the container of ChemicalGroupRule instances.
*/
std::vector<ChemicalGroupRuleSPtr> &
ChemicalGroup::getRulesRef()
{
  return m_rules;
}

/*!
\brief Searches by \a entity for a ChemicalGroupRule instance.

Returns the \a index of the ChemicalGroupRule instance in the member list of
ChemicalGroupRule instances or -1 if not found.
*/
ChemicalGroupRuleSPtr
ChemicalGroup::findRuleByEntity(const QString &entity, std::size_t &index) const
{
  if(index >= m_rules.size())
    return nullptr;

  if(entity.isEmpty())
    return nullptr;

  std::vector<ChemicalGroupRuleSPtr>::const_iterator the_begin_iterator_cst =
    m_rules.cbegin();
  std::vector<ChemicalGroupRuleSPtr>::const_iterator the_iterator_cst =
    m_rules.cbegin() + index;
  std::vector<ChemicalGroupRuleSPtr>::const_iterator the_end_iterator_cst =
    m_rules.cend();

  while(the_iterator_cst != the_end_iterator_cst)
    {
      if((*the_iterator_cst)->getEntity() == entity)
        {
          index = std::distance(the_begin_iterator_cst, the_iterator_cst);
          return *the_iterator_cst;
        }

      ++the_iterator_cst;
    }

  return nullptr;
}

/*!
\brief Searches by \a name for a ChemicalGroupRule instance.

Returns the \a index of the ChemicalGroupRule instance in the member list of
ChemicalGroupRule instances or -1 if not found.
*/
ChemicalGroupRuleSPtr
ChemicalGroup::findRuleByName(const QString &name, std::size_t &index) const
{
  if(index >= m_rules.size())
    return nullptr;

  if(name.isEmpty())
    return nullptr;


  std::vector<ChemicalGroupRuleSPtr>::const_iterator the_begin_iterator_cst =
    m_rules.cbegin();
  std::vector<ChemicalGroupRuleSPtr>::const_iterator the_iterator_cst =
    m_rules.cbegin() + index;
  std::vector<ChemicalGroupRuleSPtr>::const_iterator the_end_iterator_cst =
    m_rules.cend();

  while(the_iterator_cst != the_end_iterator_cst)
    {
      if((*the_iterator_cst)->getName() == name)
        {
          index = std::distance(the_begin_iterator_cst, the_iterator_cst);
          return *the_iterator_cst;
        }

      ++the_iterator_cst;
    }

  return nullptr;
}

/*!
\brief Searches by \a entity and \a name for a ChemicalGroupRule instance.

Returns the \a index of the ChemicalGroupRule instance in the member list of
ChemicalGroupRule instances or -1 if not found.
*/
ChemicalGroupRuleSPtr
ChemicalGroup::findRuleByEntityAndName(const QString &entity,
                                       const QString &name,
                                       std::size_t &index) const
{
  if(index >= m_rules.size())
    return nullptr;

  if(name.isEmpty())
    return nullptr;

  std::vector<ChemicalGroupRuleSPtr>::const_iterator the_begin_iterator_cst =
    m_rules.cbegin();
  std::vector<ChemicalGroupRuleSPtr>::const_iterator the_iterator_cst =
    m_rules.cbegin() + index;
  std::vector<ChemicalGroupRuleSPtr>::const_iterator the_end_iterator_cst =
    m_rules.cend();

  while(the_iterator_cst != the_end_iterator_cst)
    {
      if((*the_iterator_cst)->getName() == name &&
         (*the_iterator_cst)->getEntity() == entity)
        {
          index = std::distance(the_begin_iterator_cst, the_iterator_cst);
          return *the_iterator_cst;
        }

      ++the_iterator_cst;
    }

  return nullptr;
}

/*!
\brief Validates this instance, setting eventual error messages in \a
error_list_p.

\note \a error_list_p is not cleared.

If the validation is successful,  m_isValid is set to true, otherwise it is set
to false. That result is returned.
*/
bool
ChemicalGroup::validate(ErrorList *error_list_p) const
{
  qsizetype error_count = error_list_p->size();

  if(m_name.isEmpty())
    {
      qDebug() << "The ChemicalGroup cannot validate with an empty name.";
      error_list_p->push_back(
        "The ChemicalGroup cannot validate with an empty name");
    }

  if(m_pka <= 0 && m_pka >= 14)
    {
      qDebug()
        << "The ChemicalGroup cannot validate with an invalid pKa value.";
      error_list_p->push_back(
        "The ChemicalGroup cannot validate with an invalid pKa value.");
    }

  m_isValid = error_list_p->size() > error_count ? false : true;

  return m_isValid;
}

/*!
\brief Returns the validity status of this instance.
 */
bool
ChemicalGroup::isValid() const
{
  return m_isValid;
}

/*!
\brief Parses the ChemicalGroup XML \a element \e{related to a \l Monomer}.

Upon parsing of the \a element (tag \code{<mnmchemgroup>}), its data
are validated and set to this ChemicalGroup instance, thus essentially
initializing it.

In an pkaphpidata definition file, the following xml structure
is encountered:

\code
<pkaphpidata>
  <monomers>
    <monomer>
      <code>A</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    <monomer>
      <code>C</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>Lateral SH2</name>
        <pka>8.3</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>never_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    \endcode

Upon parsing of the \a element, all the data are validated and set to this
ChemicalGroup instance, thus essentially initializing it. If there are
\l{ChemicalGroupRule}s associated to the ChemicalGroup element, these are
rendered also.

Returns true if parsing and validation were successful, false otherwise.
*/
bool
ChemicalGroup::renderXmlMnmElement(const QDomElement &element)
{
  // The element the parameter points to is:
  //
  //  <mnmchemgroup>
  //
  // Which means that element.tagName() == "mnmchemgroup" and that we'll
  // have to go one step down to the first child of the current node
  // in order to get to the \code<name>\endcode element.


  QDomElement child;

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

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "pka")
    return false;

  bool ok = false;
  m_pka   = child.text().toFloat(&ok);

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

  if(m_pka <= 0 || m_pka >= 14)
    return false;

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "acidcharged")
    return false;

  if(child.text() != "FALSE" && child.text() != "TRUE")
    return false;

  m_acidCharged = (child.text() == "FALSE" ? false : true);

  // And now the polrule element. There should be one, here, in fact,
  // because we are dealing with a monomer, and not a modification.

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "polrule")
    return false;

  if(child.text() == "never_trapped")
    m_polymerizationRule = Enums::ChemicalGroupTrapped::NEVER;
  else if(child.text() == "left_trapped")
    m_polymerizationRule = Enums::ChemicalGroupTrapped::LEFT;
  else if(child.text() == "right_trapped")
    m_polymerizationRule = Enums::ChemicalGroupTrapped::RIGHT;
  else
    return false;

  // And finally the chemical group rules... There might be zero, one
  // or more.

  QDomElement childChemGroupRule = child.nextSiblingElement("chemgrouprule");

  while(!childChemGroupRule.isNull())
    {
      ChemicalGroupRuleSPtr chemical_group_rule_sp =
        std::make_shared<ChemicalGroupRule>();

      if(!chemical_group_rule_sp->renderXmlElement(childChemGroupRule))
        {
          chemical_group_rule_sp.reset();
          return false;
        }

      m_rules.push_back(chemical_group_rule_sp);

      childChemGroupRule = childChemGroupRule.nextSiblingElement();
    }

  ErrorList error_list;

  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Failed to validate successfully, with errors:"
             << Utils::joinErrorList(error_list);

  return m_isValid;
}

/*!
\brief Parses the ChemicalGroup XML \a element \e{related to a \l Modif}.

Upon parsing of the \a element (tag <mnmchemgroup>), its data are validated and
set to this ChemicalGroup instance, thus essentially initializing it.

In an pkaphpidata definition file, the following xml structure
is encountered:

\code
<pkaphpidata>
  <monomers>
    <monomer>
      <code>A</code>
      <mnmchemgroup>
        <name>N-term NH2</name>
        <pka>9.6</pka>
        <acidcharged>TRUE</acidcharged>
        <polrule>left_trapped</polrule>
        <chemgrouprule>
          <entity>LE_PLM_MODIF</entity>
          <name>Acetylation</name>
          <outcome>LOST</outcome>
        </chemgrouprule>
      </mnmchemgroup>
      <mnmchemgroup>
        <name>C-term COOH</name>
        <pka>2.35</pka>
        <acidcharged>FALSE</acidcharged>
        <polrule>right_trapped</polrule>
      </mnmchemgroup>
    </monomer>
    .......
  </monomers>
  <modifs>
    <modif>
      <name>Phosphorylation</name>
      <mdfchemgroup>
        <name>none_set</name>
        <pka>1.2</pka>
        <acidcharged>FALSE</acidcharged>
      </mdfchemgroup>
      <mdfchemgroup>
        <name>none_set</name>
        <pka>6.5</pka>
        <acidcharged>FALSE</acidcharged>
      </mdfchemgroup>
    </modif>
  </modifs>
</pkaphpidata>
    \endcode

Upon parsing of the \a element, all the data are validated and set to this
ChemicalGroup instance, thus essentially initializing it. If there are
\l{ChemicalGroupRule}s associated to the ChemicalGroup element, these are
rendered also.

Returns true if parsing and validation were successful, false otherwise.
*/
bool
ChemicalGroup::renderXmlMdfElement(const QDomElement &element)
{
  QDomElement child;

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

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "pka")
    return false;

  bool ok = false;
  m_pka   = child.text().toFloat(&ok);

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

  if(m_pka <= 0 || m_pka >= 14)
    return false;

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "acidcharged")
    return false;

  if(child.text() != "FALSE" && child.text() != "TRUE")
    return false;

  m_acidCharged = (child.text() == "FALSE" ? false : true);

  ErrorList error_list;

  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Failed to validate successfully, with errors:"
             << Utils::joinErrorList(error_list);

  return m_isValid;
}

//////////////////////// ChemicalGroupProp ////////////////////////
//////////////////////// ChemicalGroupProp ////////////////////////


/*!
\class MsXpS::libXpertMassCore::ChemicalGroupProp
\inmodule libXpertMassCore
\ingroup ThePropSystem

\brief The ChemicalGroupProp class provides a Prop instance of which the member
data points to a dynamically allocated \l ChemicalGroup instance.
*/

/*!
\brief Constructs a ChemicalGroupProp instance using \a data and \a name.

The \a data pointer is set to the \l mpa_data member.
*/
ChemicalGroupProp::ChemicalGroupProp(const QString &name, ChemicalGroup *data)
{
  if(!name.isEmpty())
    m_name = name;
  else
    m_name = QString();

  mpa_data = static_cast<void *>(data);
}

/*!
\brief Constructs a ChemicalGroupProp instance as a copy of \a other.

The data in \a other are duplicated and set to this ChemicalGroupProp instance.
*/
ChemicalGroupProp::ChemicalGroupProp(const ChemicalGroupProp &other)
  : Prop(other)
{
  if(other.mpa_data != nullptr)
    {
      ChemicalGroup *chemicalGroup =
        static_cast<ChemicalGroup *>(other.mpa_data);

      mpa_data = static_cast<void *>(new ChemicalGroup(*chemicalGroup));
    }
  else
    mpa_data = nullptr;
}

/*!
\brief Destructs this ChemicalGroupProp instance.

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

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

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

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

  Prop::operator=(other);

  if(mpa_data != nullptr)
    deleteData();

  if(other.mpa_data != nullptr)
    {
      ChemicalGroup *chemicalGroup =
        static_cast<ChemicalGroup *>(other.mpa_data);

      mpa_data = static_cast<void *>(new ChemicalGroup(*chemicalGroup));
    }
  else
    mpa_data = nullptr;

  return *this;
}

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

  return new_p;
}

bool
ChemicalGroupProp::renderXmlElement([[maybe_unused]] const QDomElement &element,
                                    [[maybe_unused]] int version)
{
  return false;
}

QString
ChemicalGroupProp::formatXmlElement([[maybe_unused]] int offset,
                                    [[maybe_unused]] const QString &indent)
{
  return QString();
}

} // namespace libXpertMassCore

} // namespace MsXpS
