text: // --------------------------------------------------------------------------
  //                   OpenMS -- Open-Source Mass Spectrometry
  // --------------------------------------------------------------------------
  // Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
  // ETH Zurich, and Freie Universitaet Berlin 2002-2020.
  //
  // This software is released under a three-clause BSD license:
  //  * Redistributions of source code must retain the above copyright
  //    notice, this list of conditions and the following disclaimer.
  //  * Redistributions in binary form must reproduce the above copyright
  //    notice, this list of conditions and the following disclaimer in the
  //    documentation and/or other materials provided with the distribution.
  //  * Neither the name of any author or any participating institution
  //    may be used to endorse or promote products derived from this software
  //    without specific prior written permission.
  // For a full list of authors, refer to the file AUTHORS.
  // --------------------------------------------------------------------------
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  // ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
  // INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  //
  // --------------------------------------------------------------------------
  // $Maintainer: Mathias Walzer $
  // $Authors: Mathias Walzer, Andreas Bertsch $
  // --------------------------------------------------------------------------
  
  #include <OpenMS/FORMAT/HANDLERS/MzIdentMLHandler.h>
  
  #include <OpenMS/FORMAT/FileHandler.h>
  #include <OpenMS/CONCEPT/Constants.h>
  #include <OpenMS/SYSTEM/File.h>
  #include <OpenMS/CHEMISTRY/ModificationDefinitionsSet.h>
  #include <OpenMS/CHEMISTRY/ModificationsDB.h>
  #include <OpenMS/CONCEPT/UniqueIdGenerator.h>
  #include <OpenMS/CONCEPT/VersionInfo.h>
  #include <OpenMS/CHEMISTRY/CrossLinksDB.h>
  
  #include <boost/lexical_cast.hpp>
  #include <boost/regex.hpp>
  
  using namespace std;
  
  namespace OpenMS
  {
    namespace Internal
    {
  
      MzIdentMLHandler::MzIdentMLHandler(const Identification& id, const String& filename, const String& version, const ProgressLogger& logger) :
        XMLHandler(filename, version),
        logger_(logger),
        //~ ms_exp_(0),
        id_(nullptr),
        cid_(&id)
      {
        cv_.loadFromOBO("PSI-MS", File::find("/CV/psi-ms.obo"));
        unimod_.loadFromOBO("PSI-MS", File::find("/CV/unimod.obo"));
      }
  
      MzIdentMLHandler::MzIdentMLHandler(Identification& id, const String& filename, const String& version, const ProgressLogger& logger) :
        XMLHandler(filename, version),
        logger_(logger),
        //~ ms_exp_(0),
        id_(&id),
        cid_(nullptr)
      {
        cv_.loadFromOBO("PSI-MS", File::find("/CV/psi-ms.obo"));
        unimod_.loadFromOBO("PSI-MS", File::find("/CV/unimod.obo"));
      }
  
      MzIdentMLHandler::MzIdentMLHandler(const std::vector<ProteinIdentification>& pro_id, const std::vector<PeptideIdentification>& pep_id, const String& filename, const String& version, const ProgressLogger& logger) :
        XMLHandler(filename, version),
        logger_(logger),
        //~ ms_exp_(0),
        pro_id_(nullptr),
        pep_id_(nullptr),
        cpro_id_(&pro_id),
        cpep_id_(&pep_id)
      {
        cv_.loadFromOBO("PSI-MS", File::find("/CV/psi-ms.obo"));
        unimod_.loadFromOBO("PSI-MS", File::find("/CV/unimod.obo"));
      }
  
      MzIdentMLHandler::MzIdentMLHandler(std::vector<ProteinIdentification>& pro_id, std::vector<PeptideIdentification>& pep_id, const String& filename, const String& version, const ProgressLogger& logger) :
        XMLHandler(filename, version),
        logger_(logger),
        //~ ms_exp_(0),
        pro_id_(&pro_id),
        pep_id_(&pep_id),
        cpro_id_(nullptr),
        cpep_id_(nullptr)
      {
        cv_.loadFromOBO("PSI-MS", File::find("/CV/psi-ms.obo"));
        unimod_.loadFromOBO("PSI-MS", File::find("/CV/unimod.obo"));
      }
  
      //~ TODO create MzIdentML instances from MSExperiment which contains much of the information yet needed
      //~ MzIdentMLHandler(const PeakMap& mx, const String& filename, const String& version, const ProgressLogger& logger)
      //~ : XMLHandler(filename, version),
      //~ logger_(logger),
      //~ ms_exp_(mx),
      //~ pro_id_(0),
      //~ pepid_(0),
      //~ cpepid_(0),
      //~ cpro_id_(0)
      //~ {
      //~ cv_.loadFromOBO("MS",File::find("/CV/psi-ms.obo"));
      //~ unimod_.loadFromOBO("PSI-MS",File::find("/CV/unimod.obo"));
      //~ }
  
      MzIdentMLHandler::~MzIdentMLHandler()
      {
      }
  
      void MzIdentMLHandler::startElement(const XMLCh* const /*uri*/, const XMLCh* const /*local_name*/, const XMLCh* const qname, const xercesc::Attributes& attributes)
      {
        tag_ = sm_.convert(qname);
        open_tags_.push_back(tag_);
  
        static set<String> to_ignore;
        if (to_ignore.empty())
        {
          to_ignore.insert("peptideSequence");
        }
  
        if (to_ignore.find(tag_) != to_ignore.end())
        {
          return;
        }
  
        //determine parent tag
        String parent_tag;
        if (open_tags_.size() > 1)
        {
          parent_tag = *(open_tags_.end() - 2);
        }
        String parent_parent_tag;
        if (open_tags_.size() > 2)
        {
          parent_parent_tag = *(open_tags_.end() - 3);
        }
  
        if (tag_ == "cvParam")
        {
          static const XMLCh* s_value = xercesc::XMLString::transcode("value");
          static const XMLCh* s_unit_accession = xercesc::XMLString::transcode("unitAccession");
          static const XMLCh* s_cv_ref = xercesc::XMLString::transcode("cvRef");
          //~ static const XMLCh* s_name = xercesc::XMLString::transcode("name");
          static const XMLCh* s_accession = xercesc::XMLString::transcode("accession");
  
          String value, unit_accession, cv_ref;
          optionalAttributeAsString_(value, attributes, s_value);
          optionalAttributeAsString_(unit_accession, attributes, s_unit_accession);
          optionalAttributeAsString_(cv_ref, attributes, s_cv_ref);
          handleCVParam_(parent_parent_tag, parent_tag, attributeAsString_(attributes, s_accession), /* attributeAsString_(attributes, s_name), value, */ attributes, cv_ref /*,  unit_accession */);
          return;
        }
  
        if (tag_ == "MzIdentML")
        {
          // TODO handle version with mzid 1.2 release
          return;
        }
  
        if (tag_ == "Peptide")
        {
          // start new peptide
          actual_peptide_ = AASequence();
  
          // name attribute (opt)
          String name;
          if (optionalAttributeAsString_(name, attributes, "name"))
          {
            // TODO save name in AASequence
          }
  
          return;
        }
  
        if (tag_ == "Modification")
        {
          // average mass delta attribute (opt)
          // TODO
  
          // location attribute (opt)
          Int mod_location = -1;
          if (optionalAttributeAsInt_(mod_location, attributes, "location"))
          {
            current_mod_location_ = mod_location;
          }
          else
          {
            current_mod_location_ = -1;
          }
  
          // monoisotopic mass delta attribute (opt)
          // TODO
  
          // residues attribute (opt)
          // TODO
          return;
        }
  
        if (tag_ == "SpectrumIdentificationList")
        {
  
          return;
        }
  
        if (tag_ == "SpectrumIdentificationResult")
        {
  
          return;
        }
  
        if (tag_ == "SpectrumIdentificationItem")
        {
          //  <SpectrumIdentificationItem id="SII_1_1"  calculatedMassToCharge="670.86261" chargeState="2" experimentalMassToCharge="671.9" Peptide_ref="peptide_1_1" rank="1" passThreshold="true">
          // required attributes
          current_id_hit_.setId((attributeAsString_(attributes, "id")));
          current_id_hit_.setPassThreshold(asBool_(attributeAsString_(attributes, "passThreshold")));
          current_id_hit_.setRank(attributeAsInt_(attributes, "rank"));
  
          // optional attributes
          double double_value(0);
          if (optionalAttributeAsDouble_(double_value, attributes, "calculatedMassToCharge"))
          {
            current_id_hit_.setCalculatedMassToCharge(double_value);
          }
  
          Int int_value(0);
          if (optionalAttributeAsInt_(int_value, attributes, "chargeState"))
          {
            current_id_hit_.setCharge(int_value);
          }
  
          if (optionalAttributeAsDouble_(double_value, attributes, "experimentalMassToCharge"))
          {
            current_id_hit_.setExperimentalMassToCharge(double_value);
          }
  
          if (optionalAttributeAsDouble_(double_value, attributes, "calculatedMassToCharge"))
          {
            current_id_hit_.setCalculatedMassToCharge(double_value);
          }
  
          String string_value("");
          if (optionalAttributeAsString_(string_value, attributes, "name"))
          {
            current_id_hit_.setName(string_value);
          }
  
          // TODO PeptideEvidence, pf:cvParam, pf:userParam, Fragmentation
  
          return;
        }
        error(LOAD, "MzIdentMLHandler::startElement: Unkown element found: '" + tag_ + "' in tag '" + parent_tag + "', ignoring.");
      }
  
      void MzIdentMLHandler::characters(const XMLCh* const chars, const XMLSize_t /*length*/)
      {
        if (tag_ == "Customizations")
        {
          String customizations = sm_.convert(chars);
          // TODO write customizations to Software
          return;
        }
  
        if (tag_ == "seq")
        {
          String seq = sm_.convert(chars);
          actual_protein_.setSequence(seq);
          return;
        }
  
        if (tag_ == "peptideSequence")
        {
          String pep = sm_.convert(chars);
          actual_peptide_ = AASequence::fromString(pep);
          return;
        }
  
        //error(LOAD, "MzIdentMLHandler::characters: Unkown character section found: '" + tag_ + "', ignoring.");
      }
  
      void MzIdentMLHandler::endElement(const XMLCh* const /*uri*/, const XMLCh* const /*local_name*/, const XMLCh* const qname)
      {
        static set<String> to_ignore;
        if (to_ignore.empty())
        {
          to_ignore.insert("mzIdentML");
          to_ignore.insert("cvParam");
        }
  
        tag_ = sm_.convert(qname);
        open_tags_.pop_back();
  
        if (to_ignore.find(tag_) != to_ignore.end())
        {
          return;
        }
  
        if (tag_ == "DataCollection")
        {
          return;
        }
  
        if (tag_ == "AnalysisData")
        {
          return;
        }
  
        if (tag_ == "ProteinDetectionList")
        {
          return;
        }
  
        if (tag_ == "SpectrumIdentificationList")
        {
          return;
        }
  
        if (tag_ == "SpectrumIdentificationResult")
        {
          return;
        }
  
        if (tag_ == "SpectrumIdentificationItem")
        {
          current_spectrum_id_.addHit(current_id_hit_);
          current_id_hit_ = IdentificationHit();
          return;
        }
        error(LOAD, "MzIdentMLHandler::endElement: Unkown element found: '" + tag_ + "', ignoring.");
      }
  
      void MzIdentMLHandler::handleCVParam_(const String& /* parent_parent_tag*/, const String& parent_tag, const String& accession, /* const String& name, */ /* const String& value, */ const xercesc::Attributes& attributes, const String& cv_ref /* , const String& unit_accession */)
      {
        if (parent_tag == "Modification")
        {
          if (cv_ref == "UNIMOD")
          {
            set<const ResidueModification*> mods;
            Int loc = numeric_limits<Int>::max();
            if (optionalAttributeAsInt_(loc, attributes, "location"))
            {
              String uni_mod_id = accession.suffix(':');
              String residues;
              if (optionalAttributeAsString_(residues, attributes, "residues"))
              {
                // TODO handle ambiguous/multiple residues
              }
              if (loc == 0)
              {
                ModificationsDB::getInstance()->searchModifications(mods, uni_mod_id, "", ResidueModification::N_TERM);
              }
              else if (loc == (Int)actual_peptide_.size())
              {
                ModificationsDB::getInstance()->searchModifications(mods, uni_mod_id, "", ResidueModification::C_TERM);
              }
              else
              {
                ModificationsDB::getInstance()->searchModifications(mods, uni_mod_id, residues, ResidueModification::ANYWHERE);
              }
            }
            else
            {
              warning(LOAD, "location of modification not defined!");
            }
            if (mods.empty())
            {
              String message = String("Modification '") + accession + "' is unknown.";
              throw Exception::ElementNotFound(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION, message);
            }
          }
        }
      }
  
      void MzIdentMLHandler::writeTo(std::ostream& os)
      {
        String cv_ns = cv_.name();
        String inputs_element;
        std::map<String,String> /* peps, pepevis, */ sil_map, sil_2_date;
        std::set<String> sen_set, sof_set, sip_set;
        std::map<String, String> sdb_ids, sen_ids, sof_ids, sdat_ids, pep_ids;
        //std::map<String, String> pep_pairs_ppxl;
        std::map<String, double> pp_identifier_2_thresh;
        //std::vector< std::pair<String, String> > pepid_pairs_ppxl;
  
        // file type-specific definitions needed for SpectraData element:
        std::map<FileTypes::Type, std::pair<String, String> > formats_map;
        formats_map[FileTypes::MZML] = make_pair("mzML format", "mzML unique identifier");
        formats_map[FileTypes::MZXML] = make_pair("ISB mzXML format", "scan number only nativeID format");
        formats_map[FileTypes::MZDATA] = make_pair("PSI mzData format", "spectrum identifier nativeID format");
        formats_map[FileTypes::MGF] = make_pair("Mascot MGF format", "multiple peak list nativeID format");
  
  
        //TODO if constructed with a msexperiment - not yet implemented
        //~ if(ms_exp_ == 0)
        //~ {
        //~ synthesize spectrum references
        //~ }
        //~ else
        //~ {
        //~ extract peptide and proteinid from msexperiment
        //~ genereate spectrum references from msexperiment foreach peptideidentification
        //~ }
  
        /*---------------------------------------------------------------------
        DataCollection:
        +Inputs
        -AnalysisData collected in sidlist --> unclosed element string
        ---------------------------------------------------------------------*/
        inputs_element += String("\t<Inputs>\n");
        String spectra_data, search_database;
  
        /*
        1st: iterate over proteinidentification vector
        */
        //TODO read type of crosslink reagens from settings
        bool is_ppxl = false;
        for (std::vector<ProteinIdentification>::const_iterator it = cpro_id_->begin(); it != cpro_id_->end(); ++it)
        {
          //~ collect analysissoftware in this loop - does not go into inputelement
          String sof_id;
          String sof_name = String(it->getSearchEngine());
          std::map<String, String>::iterator soit = sof_ids.find(sof_name);
          String osecv;
          if (sof_name == "OMSSA")
          {
            osecv = "OMSSA";
          }
          else if (sof_name == "Mascot")
          {
            osecv = "Mascot";
          }
          else if (sof_name == "XTandem")
          {
            osecv = "X\\!Tandem";
          }
          else if (sof_name == "SEQUEST")
          {
            osecv = "Sequest";
          }
          else if (sof_name == "MS-GF+")
          {
            osecv = "MS-GF+";
          }
          else if (sof_name == "Percolator")
          {
            osecv = "Percolator";
          }
          else if (sof_name == "OpenPepXL")
          {
            osecv = "OpenPepXL";
          }
          else if (cv_.hasTermWithName(sof_name))
          {
            osecv = sof_name;
          }
          else
          {
            osecv = "analysis software";
          }
  
          if (soit == sof_ids.end())
          {
            sof_id = "SOF_" + String(UniqueIdGenerator::getUniqueId());
            //~ TODO consider not only searchengine but also version!
            String sost = String("\t<AnalysisSoftware version=\"") + String(it->getSearchEngineVersion()) + String("\" name=\"") + sof_name +  String("\" id=\"") + sof_id + String("\">\n") + String("\t\t<SoftwareName>\n");
            sost += "\t\t\t" + cv_.getTermByName(osecv).toXMLString(cv_ns);
            sost += String("\n\t\t</SoftwareName>\n\t</AnalysisSoftware>\n");
            sof_set.insert(sost);
            sof_ids.insert(make_pair(sof_name, sof_id));
          }
          else
          {
            sof_id = soit->second;
          }
  
          if (it->metaValueExists("is_cross_linking_experiment") ||
                  (it->metaValueExists("SpectrumIdentificationProtocol") &&
                  it->getMetaValue("SpectrumIdentificationProtocol") == "MS:1002494"))
          {
            is_ppxl = true;  //needed as incoming data is structured differently and output deviates as well for ppxl
            // ppxl is like (1PeptideIdentification, 1-2 PeptideHits) but there might be more PeptideIdentifications for one spectrum
          }
  
          String thcv;
          pp_identifier_2_thresh.insert(make_pair(it->getIdentifier(),it->getSignificanceThreshold()));
          if (it->getSignificanceThreshold() != 0.0)
          {
            thcv = cv_.getTermByName("PSM-level statistical threshold").toXMLString(cv_ns, String(it->getSignificanceThreshold()));
          }
          else
          {
            thcv = cv_.getTermByName("no threshold").toXMLString(cv_ns);
          }
          // TODO add other software than searchengine for evidence trace
  
          // get a map from identifier to match OpenMS Protein/PeptideIdentification match string;
          String sil_id =  "SIL_" + String(UniqueIdGenerator::getUniqueId());
          pp_identifier_2_sil_.insert(make_pair(it->getIdentifier(), sil_id));
  
          //~ collect SpectrumIdentificationProtocol for analysisprotocol in this loop - does not go into inputelement
          String sip_id = "SIP_" + String(UniqueIdGenerator::getUniqueId());
          sil_2_sip_.insert(make_pair(sil_id, sip_id));
  
          String sip = "\t<SpectrumIdentificationProtocol id=\"" + String(sip_id) + "\" analysisSoftware_ref=\"" + String(sof_id) + "\">\n";
          sip += "\t\t<SearchType>\n\t\t\t" + cv_.getTermByName("ms-ms search").toXMLString(cv_ns) + "\n\t\t</SearchType>\n";
          sip += "\t\t<AdditionalSearchParams>\n";
          if (is_ppxl)
          {
            sip += "\n\t\t\t" + cv_.getTermByName("cross-linking search").toXMLString(cv_ns) + "\n";
          }
          //remove MS:1001029 written if present in <SearchDatabase> as of SearchDatabase_may rule
          ProteinIdentification::SearchParameters search_params = it->getSearchParameters();
          search_params.removeMetaValue("MS:1001029");
          writeMetaInfos_(sip, search_params, 3);
          sip += String(3, '\t') + "<userParam name=\"charges\" unitName=\"xsd:string\" value=\"" + search_params.charges + "\"/>\n";
  //        sip += String(3, '\t') + "<userParam name=\"" + "missed_cleavages" + "\" unitName=\"" + "xsd:integer" + "\" value=\"" + String(it->getSearchParameters().missed_cleavages) + "\"/>" + "\n";
          sip += "\t\t</AdditionalSearchParams>\n";
          // modifications:
          if (search_params.fixed_modifications.empty() &&
              search_params.variable_modifications.empty()
              && (!is_ppxl)) // TODO some OpenPepXL modifications are not covered by the unimod.obo and cause problems in the search_params
          {
            // no modifications used or are they just missing from the parameters?
            ModificationDefinitionsSet mod_defs;
            mod_defs.inferFromPeptides(*cpep_id_);
            mod_defs.getModificationNames(search_params.fixed_modifications,
                                          search_params.variable_modifications);
          }
          if (!search_params.fixed_modifications.empty() ||
              !search_params.variable_modifications.empty())
          {
            sip += "\t\t<ModificationParams>\n";
            writeModParam_(sip, search_params.fixed_modifications, true, 2);
            writeModParam_(sip, search_params.variable_modifications, false, 2);
            sip += "\t\t</ModificationParams>\n";
          }
          writeEnzyme_(sip, search_params.digestion_enzyme, search_params.missed_cleavages, 2);
          // TODO MassTable section
          sip += String("\t\t<FragmentTolerance>\n");
          String unit_str = "unitCvRef=\"UO\" unitName=\"dalton\" unitAccession=\"UO:0000221\"";
          if (search_params.fragment_mass_tolerance_ppm)
          {
            unit_str = "unitCvRef=\"UO\" unitName=\"parts per million\" unitAccession=\"UO:0000169\"";
          }
          sip += String(3, '\t') + "<cvParam accession=\"MS:1001412\" name=\"search tolerance plus value\" " + unit_str + " cvRef=\"PSI-MS\" value=\"" + String(search_params.fragment_mass_tolerance) + "\"/>\n";
          sip += String(3, '\t') + "<cvParam accession=\"MS:1001413\" name=\"search tolerance minus value\" " + unit_str + " cvRef=\"PSI-MS\" value=\"" + String(search_params.fragment_mass_tolerance) + "\"/>\n";
          sip += String("\t\t</FragmentTolerance>\n");
          sip += String("\t\t<ParentTolerance>\n");
          unit_str = "unitCvRef=\"UO\" unitName=\"dalton\" unitAccession=\"UO:0000221\"";
          if (search_params.precursor_mass_tolerance_ppm)
          {
            unit_str = "unitCvRef=\"UO\" unitName=\"parts per million\" unitAccession=\"UO:0000169\"";
          }
          sip += String(3, '\t') + "<cvParam accession=\"MS:1001412\" name=\"search tolerance plus value\" " + unit_str + " cvRef=\"PSI-MS\" value=\"" + String(search_params.precursor_mass_tolerance) + "\"/>\n";
          sip += String(3, '\t') + "<cvParam accession=\"MS:1001413\" name=\"search tolerance minus value\" " + unit_str + " cvRef=\"PSI-MS\" value=\"" + String(search_params.precursor_mass_tolerance) + "\"/>\n";
          sip += String("\t\t</ParentTolerance>\n");
          sip += String("\t\t<Threshold>\n\t\t\t") + thcv + "\n";
          sip += String("\t\t</Threshold>\n");
          sip += String("\t</SpectrumIdentificationProtocol>\n");
          sip_set.insert(sip);
          // empty date would lead to XML schema validation error:
          DateTime date_time = it->getDateTime();
          if (!date_time.isValid()) date_time = DateTime::now();
          sil_2_date.insert(make_pair(sil_id, String(date_time.getDate() + "T" + date_time.getTime())));
  
          //~ collect SpectraData element for each ProteinIdentification
          String sdat_id;
          StringList sdat_files;
          String sdat_file(it->getMetaValue("spectra_data"));
  
          if (sdat_file.empty())
          {
            sdat_file = String("UNKNOWN");
          }
          else
          {
            sdat_file = trimOpenMSfileURI(sdat_file);
          }
          std::map<String, String>::iterator sdit = sdat_ids.find(sdat_file); //this part is strongly connected to AnalysisCollection write part
          if (sdit == sdat_ids.end())
          {
            sdat_id = "SDAT_" + String(UniqueIdGenerator::getUniqueId());
  
            FileTypes::Type type = FileHandler::getTypeByFileName(sdat_file);
            if (formats_map.find(type) == formats_map.end()) type = FileTypes::MZML; // default
  
            //xml
            spectra_data += String("\t\t<SpectraData location=\"") + sdat_file + String("\" id=\"") + sdat_id + String("\">");
            spectra_data += String("\n\t\t\t<FileFormat>\n");
            spectra_data += String(4, '\t') + cv_.getTermByName(formats_map[type].first).toXMLString(cv_ns);
            spectra_data += String("\n\t\t\t</FileFormat>\n\t\t\t<SpectrumIDFormat>\n");
            spectra_data += String(4, '\t') + cv_.getTermByName(formats_map[type].second).toXMLString(cv_ns);
            spectra_data += String("\n\t\t\t</SpectrumIDFormat>\n\t\t</SpectraData>\n");
  
            sdat_ids.insert(make_pair(sdat_file, sdat_id));
            ph_2_sdat_.insert(make_pair(it->getIdentifier(), sdat_id));
          }
          else
          {
            sdat_id = sdit->second;
          }
          sil_2_sdat_.insert(make_pair(sil_id,  sdat_id));
  
          //~ collect SearchDatabase element for each ProteinIdentification
          String sdb_id;
          String sdb_file(search_params.db); //TODO @mths for several IdentificationRuns this must be something else, otherwise for two of the same db just one will be created
          std::map<String, String>::iterator dbit = sdb_ids.find(sdb_file);
          if (dbit == sdb_ids.end())
          {
            sdb_id = "SDB_"+ String(UniqueIdGenerator::getUniqueId());
  
            search_database += String("\t\t<SearchDatabase ");
            search_database += String("location=\"") + sdb_file + "\" ";
            if (!String(search_params.db_version).empty())
            {
              search_database += String("version=\"") + String(search_params.db_version) + "\" ";
            }
            search_database += String("id=\"") + sdb_id + String("\">\n\t\t\t<FileFormat>\n");
            //TODO Searchdb file format type cvParam handling
            search_database += String(4, '\t') + cv_.getTermByName("FASTA format").toXMLString(cv_ns);
            search_database += String("\n\t\t\t</FileFormat>\n\t\t\t<DatabaseName>\n\t\t\t\t<userParam name=\"") + sdb_file + String("\"/>\n\t\t\t</DatabaseName>\n");
            // "MS:1001029" was removed from the "search_params" copy!
            if (it->getSearchParameters().metaValueExists("MS:1001029"))
            {
              search_database += String(3, '\t') + cv_.getTerm("MS:1001029").toXMLString(cv_ns, it->getSearchParameters().getMetaValue("MS:1001029")) + String("\n");
            }
            search_database += "\t\t</SearchDatabase>\n";
  
            sdb_ids.insert(make_pair(sdb_file, sdb_id));
          }
          else
          {
            sdb_id = dbit->second;
          }
          sil_2_sdb_.insert(make_pair(sil_id, sdb_id));
  
  
          for (std::vector<ProteinHit>::const_iterator jt = it->getHits().begin(); jt != it->getHits().end(); ++jt)
          {
            String enid;
            std::map<String, String>::iterator enit = sen_ids.find(String(jt->getAccession()));
            if (enit == sen_ids.end())
            {
              String entry;
              enid = "PROT_" + String(UniqueIdGenerator::getUniqueId()); //TODO IDs from metadata or where its stored at read in;
              String enst(jt->getAccession());
  
              entry += "\t<DBSequence accession=\"" + enst + "\" ";
              entry += "searchDatabase_ref=\"" + sdb_id + "\" ";
              String s = String(jt->getSequence());
              if (!s.empty())
              {
                entry += "length=\"" + String(jt->getSequence().length()) + "\" ";
              }
              entry += String("id=\"") + String(enid) + String("\">\n");
              if (!s.empty())
              {
                entry += "\t<Seq>" + s + "</Seq>\n";
              }
              entry += "\t\t" + cv_.getTermByName("protein description").toXMLString(cv_ns, enst);
              entry += "\n\t</DBSequence>\n";
  
              sen_ids.insert(std::pair<String, String>(enst, enid));
              sen_set.insert(entry);
  
            }
            else
            {
              enid = enit->second;
            }
          }
        }
        inputs_element += search_database;
        inputs_element += spectra_data;
        inputs_element += "\t</Inputs>\n";
  
        /*
        2nd: iterate over peptideidentification vector
        */
        //TODO ppxl - write here "MS:1002511" Cross-linked spectrum identification item linking the other spectrum
        //          PeptideIdentification repräsentiert xl paar.
        //          PeptideHit score_type ist dann final score von xQuest_cpp.
        //          top5 ids -> 5 PeptideIdentification für ein (paar) spectra. SIR with 5 entries and ranks
        std::map<String, String> ppxl_specref_2_element; //where the SII will get added for one spectrum reference
        std::map<String, std::vector<String> > pep_evis; //maps the sequence to the corresponding evidence elements for the next scope
        for (std::vector<PeptideIdentification>::const_iterator it = cpep_id_->begin(); it != cpep_id_->end(); ++it)
        {
          String emz(it->getMZ());
          const double rt = it->getRT();
          String ert = rt == rt ? String(rt) : "nan";
  
          String sid = it->getMetaValue(Constants::UserParam::SPECTRUM_REFERENCE);
          if (sid.empty())
          {
            sid = String(it->getMetaValue("spectrum_id"));
            if (sid.empty())
            {
                if (it->getMZ() != it->getMZ())
              {
                emz = "nan";
                OPENMS_LOG_WARN << "Found no spectrum reference and no m/z position of identified spectrum! You are probably converting from an old format with insufficient data provision. Setting 'nan' - downstream applications might fail unless you set the references right." << std::endl;
              }
              if (it->getRT() != it->getRT())
              {
                ert = "nan";
                OPENMS_LOG_WARN << "Found no spectrum reference and no RT position of identified spectrum! You are probably converting from an old format with insufficient data provision. Setting 'nan' - downstream applications might fail unless you set the references right." << std::endl;
              }
              sid = String("MZ:") + emz + String("@RT:") + ert;
            }
          }
          if (is_ppxl && it->metaValueExists(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_REF))
          {
            sid.append("," + String(it->getMetaValue(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_REF)));
          }
          String sidres;
          String sir = "SIR_" + String(UniqueIdGenerator::getUniqueId());
          String sdr = sdat_ids.begin()->second;
          std::map<String, String>::iterator pfo = ph_2_sdat_.find(it->getIdentifier());
          if (pfo != ph_2_sdat_.end())
          {
            sdr = pfo->second;
          }
          else
          {
            OPENMS_LOG_WARN << "Falling back to referencing first spectrum file given because file or identifier could not be mapped." << std::endl;
          }
  
          sidres += String("\t\t\t<SpectrumIdentificationResult spectraData_ref=\"")
          //multi identification runs lookup from file_origin here
                  + sdr + String("\" spectrumID=\"")
                  + sid + String("\" id=\"") + sir + String("\">\n");
  
          if (is_ppxl)
          {
            if (ppxl_specref_2_element.find(sid)==ppxl_specref_2_element.end())
            {
              ppxl_specref_2_element[sid] = sidres;
            }
          }
  
          //map.begin access ok here because make sure at least one "UNKOWN" element is in the sdats_ids map
  
          double ppxl_crosslink_mass(0);
          if (is_ppxl)
          {
            ProteinIdentification::SearchParameters search_params = cpro_id_->front().getSearchParameters();
            ppxl_crosslink_mass = String(search_params.getMetaValue("cross_link:mass")).toDouble();
          }
  
          for (std::vector<PeptideHit>::const_iterator jt = it->getHits().begin(); jt != it->getHits().end(); ++jt)
          {
            if (!is_ppxl)
            {
              MzIdentMLHandler::writePeptideHit(*jt, it, pep_ids, cv_ns, sen_set, sen_ids, pep_evis, pp_identifier_2_thresh, sidres);
            }
            else
            {
              String ppxl_linkid = UniqueIdGenerator::getUniqueId();
              MzIdentMLHandler::writeXLMSPeptideHit(*jt, it, ppxl_linkid, pep_ids, cv_ns, sen_set, sen_ids, pep_evis, pp_identifier_2_thresh, ppxl_crosslink_mass, ppxl_specref_2_element, sid, true);
              // XL-MS IDs from OpenPepXL can have two Peptides and SpectrumIdentifications, but with practically the same data except for the sequence and its modifications
              if (jt->metaValueExists(Constants::UserParam::OPENPEPXL_XL_TYPE) && jt->getMetaValue(Constants::UserParam::OPENPEPXL_XL_TYPE) == "cross-link")
              {
                MzIdentMLHandler::writeXLMSPeptideHit(*jt, it, ppxl_linkid, pep_ids, cv_ns, sen_set, sen_ids, pep_evis, pp_identifier_2_thresh, ppxl_crosslink_mass, ppxl_specref_2_element, sid, false);
              }
            }
          }
          if (!ert.empty() && ert != "nan" && ert != "NaN" && !is_ppxl)
          {
            DataValue rtcv(ert);
            rtcv.setUnit(10); // id: UO:0000010 name: second
            rtcv.setUnitType(DataValue::UnitType::UNIT_ONTOLOGY);
            sidres +=  "\t\t\t\t" + cv_.getTermByName("retention time").toXMLString(cv_ns, rtcv) + "\n";
          }
          if (!is_ppxl)
          {
            sidres += "\t\t\t</SpectrumIdentificationResult>\n";
            std::map<String, String>::const_iterator ps_it = pp_identifier_2_sil_.find(it->getIdentifier());
            if (ps_it != pp_identifier_2_sil_.end())
            {
              std::map<String, String>::iterator sil_it = sil_map.find(ps_it->second);
              if (sil_it != sil_map.end())
              {
                sil_it->second.append(sidres);
              }
              else
              {
                sil_map.insert(make_pair(ps_it->second,sidres));
              }
            }
            else
            {
              //encountered a PeptideIdentification which is not linked to any ProteinIdentification
              OPENMS_LOG_ERROR << "encountered a PeptideIdentification which is not linked to any ProteinIdentification" << std::endl;
            }
          }
  
        }
        // ppxl - write spectrumidentificationresult closing tags!
        if (is_ppxl)
        {
          for (std::map<String, String>::iterator it = ppxl_specref_2_element.begin(); it != ppxl_specref_2_element.end(); ++it)
          {
            it->second += "\t\t\t</SpectrumIdentificationResult>\n";
            std::map<String, String>::const_iterator ps_it = pp_identifier_2_sil_.begin();
  
            if (ps_it != pp_identifier_2_sil_.end())
            {
              std::map<String, String>::iterator sil_it = sil_map.find(ps_it->second);
              if (sil_it != sil_map.end())
              {
                sil_it->second.append(it->second);
              }
              else
              {
                sil_map.insert(make_pair(ps_it->second,it->second));
              }
            }
            else
            {
              //encountered a PeptideIdentification which is not linked to any ProteinIdentification
              OPENMS_LOG_ERROR << "encountered a PeptideIdentification crosslink information which is not linked to any ProteinIdentification" << std::endl;
            }
          }
        }
  
        //--------------------------------------------------------------------------------------------
        // XML header
        //--------------------------------------------------------------------------------------------
        String v_s = "1.1.0";
        if (is_ppxl)
        {
           v_s = "1.2.0";
        }
        os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
           << "<MzIdentML xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
           << "\txsi:schemaLocation=\"http://psidev.info/psi/pi/mzIdentML/"<< v_s.substr(0,v_s.size()-2) <<" "
           << "https://raw.githubusercontent.com/HUPO-PSI/mzIdentML/master/schema/mzIdentML"<< v_s <<".xsd\"\n"
           << "\txmlns=\"http://psidev.info/psi/pi/mzIdentML/"<< v_s.substr(0,v_s.size()-2) <<"\"\n"
           << "\tversion=\"" << v_s << "\"\n";
        os << "\tid=\"OpenMS_" << String(UniqueIdGenerator::getUniqueId()) << "\"\n"
           << "\tcreationDate=\"" << DateTime::now().getDate() << "T" << DateTime::now().getTime() << "\">\n";
  
        //--------------------------------------------------------------------------------------------
        // CV list
        //--------------------------------------------------------------------------------------------
        os << "<cvList>\n"
           << "\t<cv id=\"PSI-MS\" fullName=\"Proteomics Standards Initiative Mass Spectrometry Vocabularies\" "
           << "uri=\"https://raw.githubusercontent.com/HUPO-PSI/psi-ms-CV/master/psi-ms.obo\" "
           << "version=\"3.15.0\"></cv>\n "
           << "\t<cv id=\"UNIMOD\" fullName=\"UNIMOD\" uri=\"http://www.unimod.org/obo/unimod.obo\"></cv>\n"
           << "\t<cv id=\"UO\"     fullName=\"UNIT-ONTOLOGY\" "
           << "uri=\"https://raw.githubusercontent.com/bio-ontology-research-group/unit-ontology/master/unit.obo\"></cv>\n";
        if (is_ppxl)
        {
            os << "\t<cv id=\"XLMOD\" fullName=\"PSI cross-link modifications\" "
               << "uri=\"https://raw.githubusercontent.com/HUPO-PSI/mzIdentML/master/cv/XLMOD-1.0.0.obo\"></cv>\n";
        }
        os << "</cvList>\n";
  
        //--------------------------------------------------------------------------------------------
        // AnalysisSoftwareList
        //--------------------------------------------------------------------------------------------
        os << "<AnalysisSoftwareList>\n";
        for (std::set<String>::const_iterator sof = sof_set.begin(); sof != sof_set.end(); ++sof)
        {
          os << *sof;
        }
  
        std::map<String, String>::iterator soit = sof_ids.find("TOPP software");
        if (soit == sof_ids.end())
        {
          os << "\t<AnalysisSoftware version=\"OpenMS TOPP v"<< VersionInfo::getVersion() <<"\" name=\"TOPP software\" id=\"" << String("SOF_") << String(UniqueIdGenerator::getUniqueId()) << "\">\n"
             << "\t\t<SoftwareName>\n\t\t\t" << cv_.getTermByName("TOPP software").toXMLString(cv_ns) << "\n\t\t</SoftwareName>\n\t</AnalysisSoftware>\n";
        }
        os << "</AnalysisSoftwareList>\n";
  
        //--------------------------------------------------------------------------------------------
        // SequenceCollection
        //--------------------------------------------------------------------------------------------
        os << "<SequenceCollection>\n";
        for (std::set<String>::const_iterator sen = sen_set.begin(); sen != sen_set.end(); ++sen)
        {
          os << *sen;
        }
        os << "</SequenceCollection>\n";
  
        //--------------------------------------------------------------------------------------------
        // AnalysisCollection:
        // + SpectrumIdentification
        // TODO ProteinDetection
        //--------------------------------------------------------------------------------------------
        os << "<AnalysisCollection>\n";
        for (std::map<String,String>::const_iterator pp2sil_it = pp_identifier_2_sil_.begin(); pp2sil_it != pp_identifier_2_sil_.end(); ++pp2sil_it)
        {
            String entry = String("\t<SpectrumIdentification id=\"SI_") + pp2sil_it->first + String("\" spectrumIdentificationProtocol_ref=\"")
                             + sil_2_sip_[pp2sil_it->second] + String("\" spectrumIdentificationList_ref=\"") + pp2sil_it->second
                             + String("\" activityDate=\"") + sil_2_date[pp2sil_it->second]
                             + String("\">\n")
                              //if crosslink +cvparam crosslink search performed
                             + "\t\t<InputSpectra spectraData_ref=\"" + sil_2_sdat_[pp2sil_it->second] + "\"/>\n" // spd_ids.insert(std::pair<String, UInt64>(sdst, sdid));
                             + "\t\t<SearchDatabaseRef searchDatabase_ref=\"" + sil_2_sdb_[pp2sil_it->second] + "\"/>\n"
                             + "\t</SpectrumIdentification>\n";
            os <<   entry;
        }
        os << "</AnalysisCollection>\n";
  
        //--------------------------------------------------------------------------------------------
        // AnalysisProtocolCollection
        //+ SpectrumIdentificationProtocol + SearchType + Threshold
        //--------------------------------------------------------------------------------------------
        os << "<AnalysisProtocolCollection>\n";
        for (std::set<String>::const_iterator sip = sip_set.begin(); sip != sip_set.end(); ++sip)
        {
          os << *sip;
        }
        os << "</AnalysisProtocolCollection>\n";
  
        //--------------------------------------------------------------------------------------------
        // DataCollection
        //+Inputs
        //+AnalysisData
        //--------------------------------------------------------------------------------------------
        os << "<DataCollection>\n"
           << inputs_element;
        os << "\t<AnalysisData>\n";
        for (std::map<String,String>::const_iterator sil_it = sil_map.begin(); sil_it != sil_map.end(); ++sil_it)
        {
          os << "\t\t<SpectrumIdentificationList id=\"" << sil_it->first << String("\">\n");
          os << "\t\t\t<FragmentationTable>\n"
             << "\t\t\t\t<Measure id=\"Measure_mz\">\n"
             << "\t\t\t\t\t<cvParam accession=\"MS:1001225\" cvRef=\"PSI-MS\" unitCvRef=\"PSI-MS\" unitName=\"m/z\" unitAccession=\"MS:1000040\" name=\"product ion m/z\"/>\n"
             << "\t\t\t\t</Measure>\n"
             << "\t\t\t\t<Measure id=\"Measure_int\">\n"
             << "\t\t\t\t\t<cvParam cvRef=\"PSI-MS\" accession=\"MS:1001226\" name=\"product ion intensity\" unitAccession=\"MS:1000131\" unitCvRef=\"UO\" unitName=\"number of detector counts\"/>\n"
             << "\t\t\t\t</Measure>\n"
             << "\t\t\t\t<Measure id=\"Measure_error\">\n"
             << "\t\t\t\t\t<cvParam cvRef=\"PSI-MS\" accession=\"MS:1001227\" name=\"product ion m/z error\" unitAccession=\"MS:1000040\" unitCvRef=\"PSI-MS\" unitName=\"m/z\"/>\n"
             << "\t\t\t\t</Measure>\n";
          if (is_ppxl)
          {
              os << "<!-- userParam cross-link_chain will contain a list of chain type corresponding to the indexed ion [alpha|beta] -->\n";
              os << "<!-- userParam cross-link_ioncategory will contain a list of ion category corresponding to the indexed ion [xi|ci] -->\n";
          }
          os << "\t\t\t</FragmentationTable>\n";
          os << sil_it->second;
          os << "\t\t</SpectrumIdentificationList>\n";
        }
        os << "\t</AnalysisData>\n</DataCollection>\n";
  
        //--------------------------------------------------------------------------------------------
        // close XML header
        //--------------------------------------------------------------------------------------------
        os << "</MzIdentML>\n";
      }
  
      void MzIdentMLHandler::writeMetaInfos_(String& s, const MetaInfoInterface& meta, UInt indent) const
      {
        //TODO @mths: write those metas with their name in the cvs loaded as CVs!
        if (meta.isMetaEmpty())
        {
          return;
        }
        std::vector<String> keys;
        meta.getKeys(keys);
  
        for (Size i = 0; i != keys.size(); ++i)
        {
          if (cv_.exists(keys[i]))
          {
            ControlledVocabulary::CVTerm a = cv_.getTerm(keys[i]);
            s += String(indent, '\t') + a.toXMLString("PSI-MS", (String)(meta.getMetaValue(keys[i]))) + "\n";
          }
          else
          {
            s += String(indent, '\t') + "<userParam name=\"" + keys[i] + "\" unitName=\"";
  
            DataValue d = meta.getMetaValue(keys[i]);
            //determine type
            if (d.valueType() == DataValue::INT_VALUE)
            {
              s += "xsd:integer";
            }
            else if (d.valueType() == DataValue::DOUBLE_VALUE)
            {
              s += "xsd:double";
            }
            else //string or lists are converted to string
            {
              s += "xsd:string";
            }
            s += "\" value=\"" + (String)(d) + "\"/>\n";
          }
        }
      }
  
      void MzIdentMLHandler::writeEnzyme_(String& s, DigestionEnzymeProtein enzy, UInt miss, UInt indent) const
      {
        String cv_ns = cv_.name();
        s += String(indent, '\t') + "<Enzymes independent=\"false\">\n";
        s += String(indent + 1, '\t') + "<Enzyme missedCleavages=\"" + String(miss) + "\" id=\"" + String("ENZ_") + String(UniqueIdGenerator::getUniqueId()) + "\">\n";
        s += String(indent + 2, '\t') + "<EnzymeName>\n";
        String enzymename = enzy.getName();
        if (cv_.hasTermWithName(enzymename))
        {
          s += String(indent + 3, '\t') + cv_.getTermByName(enzymename).toXMLString(cv_ns) + "\n";
        }
        else if (enzymename == "no cleavage")
        {
          s += String(indent + 3, '\t') + cv_.getTermByName("NoEnzyme").toXMLString(cv_ns) + "\n";
        }
        else
        {
          s += String(indent + 3, '\t') + cv_.getTermByName("cleavage agent details").toXMLString(cv_ns) + "\n";
        }
        s += String(indent + 2, '\t') + "</EnzymeName>\n";
        s += String(indent + 1, '\t') + "</Enzyme>\n";
        s += String(indent, '\t') + "</Enzymes>\n";
      }
  
      void MzIdentMLHandler::writeModParam_(String& s, const std::vector<String>& mod_names, bool fixed, UInt indent) const
      {
        String cv_ns = unimod_.name();
        for (std::vector<String>::const_iterator it = mod_names.begin(); it != mod_names.end(); ++it)
        {
          std::set<const ResidueModification*> mods;
          ModificationsDB::getInstance()->searchModifications(mods, *it);
          if (!mods.empty())
          {
            // @TODO: if multiple mods match, we write all of them?
            for (std::set<const ResidueModification*>::const_iterator mt = mods.begin(); mt != mods.end(); ++mt)
            {
              char origin = (*mt)->getOrigin();
              if (origin == 'X') origin = '.'; // terminal without res. spec.
  
              s += String(indent + 1, '\t') + "<SearchModification fixedMod=\"" + (fixed ? "true" : "false") + "\" massDelta=\"" + String((*mt)->getDiffMonoMass()) + "\" residues=\"" + origin + "\">\n";
  
              // @TODO: handle protein C-term/N-term
              ResidueModification::TermSpecificity spec = (*mt)->getTermSpecificity();
              if ((spec == ResidueModification::C_TERM) || (spec == ResidueModification::N_TERM))
              {
                const String& cv_name = "modification specificity peptide " + (*mt)->getTermSpecificityName();
                s += String(indent + 2, '\t') + "<SpecificityRules>\n";
                s += String(indent + 3, '\t') + cv_.getTermByName(cv_name).toXMLString(cv_ns) + "\n";
                s += String(indent + 2, '\t') + "</SpecificityRules>\n";
              }
  
              String ac = (*mt)->getUniModAccession();
              if (ac.hasPrefix("UniMod:")) ac = "UNIMOD:" + ac.suffix(':');
              if (!ac.empty())
              {
                s += String(indent + 2, '\t') + unimod_.getTerm(ac).toXMLString(cv_ns) + "\n";
              }
              else
              {
                s += String(indent + 2, '\t') + "<cvParam cvRef=\"MS\" accession=\"MS:1001460\" name=\"unknown modification\"/>\n";
              }
              s += String(indent + 1, '\t') + "</SearchModification>\n";
            }
          }
          else
          {
            String message = String("Registered ") + (fixed ? "fixed" : "variable") + " modification '" + *it + "' is unknown and will be ignored.";
            throw Exception::ElementNotFound(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION, message);
          }
        }
      }
  
      void MzIdentMLHandler::writeFragmentAnnotations_(String& s, const std::vector<PeptideHit::PeakAnnotation>& annotations, UInt indent, bool is_ppxl) const
      {
        std::map<UInt,std::map<String,std::vector<StringList> > > annotation_map;
        for (std::vector<PeptideHit::PeakAnnotation>::const_iterator kt = annotations.begin();
               kt != annotations.end(); ++kt)
        {// string coding example: [alpha|ci$y3-H2O-NH3]5+
          // static const boost::regex frag_regex("\\[(?:([\\|\\w]+)\\$)*([abcxyz])(\\d+)((?:[\\+\\-\\w])*)\\](\\d+)\\+"); // this will fetch the complete loss/gain part as one
          static const boost::regex frag_regex_tweak("\\[(?:([\\|\\w]+)\\$)*([abcxyz])(\\d+)(?:-(H2O|NH3))*\\][(\\d+)\\+]*"); // this will only fetch the last loss - and is preferred for now, as only these extra cv params are present
          String ionseries_index;
          String iontype;
          //String loss_gain;
          String loss;
          StringList extra;
          boost::smatch str_matches;
          if (boost::regex_match(kt->annotation, str_matches, frag_regex_tweak))
          {
            String(str_matches[1]).split("|",extra);
            iontype = std::string(str_matches[2]);
            ionseries_index = std::string(str_matches[3]);
            loss = std::string(str_matches[4]);
          }
          else
          {
            // since PeakAnnotations are very flexible and not all of them fit into the limited mzid fragment structure,
            // this would happen quite often and flood the output, but we still need them for other output formats
            // TODO find ways to represent additional fragment types or filter out known incompatible types
  
            // OPENMS_LOG_WARN << "Well, fudge you very much, there is no matching annotation. ";
            // OPENMS_LOG_WARN << kt->annotation << std::endl;
            continue;
          }
          String lt = "frag: " + iontype + " ion";
          if (!loss.empty())
              lt += " - "+loss;
          if (annotation_map.find(kt->charge) == annotation_map.end())
          {
            annotation_map[kt->charge] = std::map<String, std::vector<StringList> >();
          }
          if (annotation_map[kt->charge].find(lt) == annotation_map[kt->charge].end())
          {
            annotation_map[kt->charge][lt] = std::vector<StringList> (3);
            if (is_ppxl)
            {
              annotation_map[kt->charge][lt].push_back(StringList());  // alpha|beta
              annotation_map[kt->charge][lt].push_back(StringList());  // ci|xi
            }
          }
          annotation_map[kt->charge][lt][0].push_back(ionseries_index);
          annotation_map[kt->charge][lt][1].push_back(String(kt->mz));
          annotation_map[kt->charge][lt][2].push_back(String(kt->intensity));
          if (is_ppxl)
          {
            String ab = ListUtils::contains<String>(extra ,String("alpha")) ? String("alpha"):String("beta");
            String cx = ListUtils::contains<String>(extra ,String("ci")) ? String("ci"):String("xi");
            annotation_map[kt->charge][lt][3].push_back(ab);
            annotation_map[kt->charge][lt][4].push_back(cx);
          }
        }
  
        // stop and return, if no mzid compatible fragments were found
        if (annotation_map.empty())
        {
          return;
        }
        //double map: charge + ion type; collect in StringList: index + annotations; write:
        s += String(indent, '\t') + "<Fragmentation>\n";
        for (std::map<UInt,std::map<String,std::vector<StringList> > >::iterator i=annotation_map.begin();
             i!=annotation_map.end(); ++i)
        {
          for (std::map<String,std::vector<StringList> >::iterator j=i->second.begin();
               j!= i->second.end(); ++j)
          {
            s += String(indent+1, '\t') + "<IonType charge=\"" + String(i->first) +"\""
                      + " index=\"" + ListUtils::concatenate(j->second[0], " ") + "\">\n";
            s += String(indent+2, '\t') + "<FragmentArray measure_ref=\"Measure_mz\""
                      + " values=\"" + ListUtils::concatenate(j->second[1], " ") + "\"/>\n";
            s += String(indent+2, '\t') + "<FragmentArray measure_ref=\"Measure_int\""
                      + " values=\"" + ListUtils::concatenate(j->second[2], " ") + "\"/>\n";
            if (is_ppxl)
            {
                s += String(indent+2, '\t') + "<userParam name=\"cross-link_chain\"" + " unitName=\"xsd:string\""
                          + " value=\"" + ListUtils::concatenate(j->second[3], " ") + "\"/>\n";
                s += String(indent+2, '\t') + "<userParam name=\"cross-link_ioncategory\"" + " unitName=\"xsd:string\""
                          + " value=\"" + ListUtils::concatenate(j->second[4], " ") + "\"/>\n";
            }
            s += String(indent+2, '\t') + cv_.getTermByName(j->first).toXMLString("PSI-MS") + "\n";
            s += String(indent+1, '\t') + "</IonType>\n";
          }
        }
        s += String(indent, '\t') + "</Fragmentation>\n";
  //<Fragmentation>
  //    <IonType charge="1" index="2 3 5 6 5 6 10">
  //        <FragmentArray measure_ref="Measure_MZ" values="363.908 511.557 754.418 853.489 377.941 427.477 633.674"/>
  //        <FragmentArray measure_ref="Measure_Int" values="208.52 2034.9 1098.44 239.26 3325.34 3028.33 335.63"/>
  //        <FragmentArray measure_ref="Measure_Error" values="-0.255 0.326 0.101 0.104 0.285 0.287 0.369"/>
  //        <cvParam accession="MS:1001118" cvRef="PSI-MS" name="param: b ion"/>
  //    </IonType>
  //    <IonType charge="1" index="0 1 3 4 5 6 4 10">
  //        <FragmentArray measure_ref="Measure_MZ" values="175.202 246.812 474.82 587.465 686.52 814.542 294.235 652.206"/>
  //        <FragmentArray measure_ref="Measure_Int" values="84.44999 90.26999 143.95 3096.84 815.34 1.15999 612.52 18.79999"/>
  //        <FragmentArray measure_ref="Measure_Error" values="0.083 0.656 0.553 0.114 0.101 0.064 0.061 0.375"/>
  //        <cvParam accession="MS:1001262" cvRef="PSI-MS" name="param: y ion"/>
  //    </IonType>
  //</Fragmentation>
      }
  
      String MzIdentMLHandler::trimOpenMSfileURI(const String file) const
      {
        String r = file;
        if (r.hasPrefix("["))
          r = r.substr(1);
        if (r.hasSuffix("]"))
          r = r.substr(0,r.size()-1);
        r.substitute("\\","/");
        return r;
      }
  
      void MzIdentMLHandler::writePeptideHit(const PeptideHit& hit,
                                                  std::vector<PeptideIdentification>::const_iterator& it,
                                                  std::map<String, String>& pep_ids,
                                                  String cv_ns, std::set<String>& sen_set,
                                                  std::map<String, String>& sen_ids,
                                                  std::map<String, std::vector<String> >& pep_evis,
                                                  std::map<String, double>& pp_identifier_2_thresh,
                                                  String& sidres)
      {
          String pepid =  "PEP_" + String(UniqueIdGenerator::getUniqueId());
          String pepi = hit.getSequence().toString();
  
          bool duplicate = false;
          std::map<String, String>::iterator pit;
  
          // avoid duplicates
          pit = pep_ids.find(pepi);
          if (pit != pep_ids.end())
          {
            duplicate = true;
          }
  
          if (!duplicate)
          {
            String p;
            //~ TODO simplify mod cv param write
            // write peptide id with conversion to universal, "human-readable" bracket string notation
            p += String("\t<Peptide id=\"") + pepid + String("\" name=\"") +
                  hit.getSequence().toBracketString(false) + String("\">\n\t\t<PeptideSequence>") + hit.getSequence().toUnmodifiedString() + String("</PeptideSequence>\n");
            if (hit.getSequence().isModified())
            {
              const ResidueModification* n_term_mod = hit.getSequence().getNTerminalModification();
              if (n_term_mod != nullptr)
              {
                p += "\t\t<Modification location=\"0\">\n";
                String acc = n_term_mod->getUniModAccession();
                p += "\t\t\t<cvParam accession=\"UNIMOD:" + acc.suffix(':');
                p += "\" name=\"" + n_term_mod->getId();
                p += "\" cvRef=\"UNIMOD\"/>";
                p += "\n\t\t</Modification>\n";
              }
              const ResidueModification* c_term_mod = hit.getSequence().getCTerminalModification();
              if (c_term_mod != nullptr)
              {
                p += "\t\t<Modification location=\"" + String(hit.getSequence().size()) + "\">\n";
                String acc = c_term_mod->getUniModAccession();
                p += "\t\t\t<cvParam accession=\"UNIMOD:" + acc.suffix(':');
                p += "\" name=\"" + c_term_mod->getId();
                p += "\" cvRef=\"UNIMOD\"/>";
                p += "\n\t\t</Modification>\n";
              }
              for (Size i = 0; i < hit.getSequence().size(); ++i)
              {
                const ResidueModification* mod = hit.getSequence()[i].getModification(); // "UNIMOD:" prefix??
                if (mod != nullptr)
                {
                  //~ p += hit.getSequence()[i].getModification() + "\t" +  hit.getSequence()[i].getOneLetterCode()  + "\t" +  x +   "\n" ;
                  p += "\t\t<Modification location=\"" + String(i + 1);
                  p += "\" residues=\"" + hit.getSequence()[i].getOneLetterCode();
                  String acc = mod->getUniModAccession();
                  if (!acc.empty())
                  {
                    p += "\">\n\t\t\t<cvParam accession=\"UNIMOD:" + acc.suffix(':'); //TODO @all: do not exclusively use unimod ...
                    p += "\" name=\"" + mod->getId();
                    p += "\" cvRef=\"UNIMOD\"/>";
                    p += "\n\t\t</Modification>\n";
                  }
                  else
                  {
                    // We have an unknown modification, so lets write unknown
                    // and at least try to write down the delta mass.
                    if (mod->getDiffMonoMass() != 0.0)
                    {
                      double diffmass = mod->getDiffMonoMass();
                      p += "\" monoisotopicMassDelta=\"" + String(diffmass);
                    }
                    else if (mod->getMonoMass() > 0.0)
                    {
                      double diffmass = mod->getMonoMass() - hit.getSequence()[i].getMonoWeight();
                      p += "\" monoisotopicMassDelta=\"" + String(diffmass);
                    }
                    p += "\">\n\t\t\t<cvParam cvRef=\"MS\" accession=\"MS:1001460\" name=\"unknown modification\"/>";
                    p += "\n\t\t</Modification>\n";
                  }
                }
              }
            }
            p += "\t</Peptide>\n ";
            sen_set.insert(p);
            pep_ids.insert(std::make_pair(pepi, pepid));
          }
          else
          {
            pepid = pit->second;
          }
  
          std::vector<String> pevid_ids;
          if (!duplicate)
          {
            std::vector<PeptideEvidence> peptide_evidences = hit.getPeptideEvidences();
            // TODO idXML allows peptide hits without protein references! Fails in that case - run PeptideIndexer first
            for (std::vector<PeptideEvidence>::const_iterator pe = peptide_evidences.begin(); pe != peptide_evidences.end(); ++pe)
            {
              String pevid =  "PEV_" + String(UniqueIdGenerator::getUniqueId());
              String dBSequence_ref;
              map<String, String>::const_iterator pos = sen_ids.find(pe->getProteinAccession());
              if (pos != sen_ids.end())
              {
                dBSequence_ref = pos->second;
              }
              else
              {
                OPENMS_LOG_ERROR << "Error: Missing or invalid protein reference for peptide '" << pepi << "': '" << pe->getProteinAccession() << "' - skipping." << endl;
                continue;
              }
              String idec;
              if (hit.metaValueExists(Constants::UserParam::TARGET_DECOY))
              {
                idec = String(boost::lexical_cast<std::string>((String(hit.getMetaValue(Constants::UserParam::TARGET_DECOY))).hasSubstring("decoy")));
              }
  
              String e;
              String nc_termini = "-";    // character for N- and C-termini as specified in mzIdentML
              e += "\t<PeptideEvidence id=\"" + pevid + "\" peptide_ref=\"" + pepid + "\" dBSequence_ref=\"" + dBSequence_ref + "\"";
  
              if (pe->getAAAfter() != PeptideEvidence::UNKNOWN_AA)
              {
                e += " post=\"" + (pe->getAAAfter() == PeptideEvidence::C_TERMINAL_AA ? nc_termini : String(pe->getAAAfter())) + "\"";
              }
              if (pe->getAABefore() != PeptideEvidence::UNKNOWN_AA)
              {
                e += " pre=\"" + (pe->getAABefore() == PeptideEvidence::N_TERMINAL_AA ? nc_termini : String(pe->getAABefore())) + "\"";
              }
              if (pe->getStart() != PeptideEvidence::UNKNOWN_POSITION)
              {
                e += " start=\"" + String(pe->getStart() + 1) + "\"";
              }
              else if (hit.metaValueExists("start"))
              {
                e += " start=\"" + String( int(hit.getMetaValue("start")) + 1) + "\"";
              }
              else
              {
                OPENMS_LOG_WARN << "Found no start position of peptide hit in protein sequence." << std::endl;
              }
              if (pe->getEnd() != PeptideEvidence::UNKNOWN_POSITION)
              {
                e += " end=\"" + String(pe->getEnd() + 1) + "\"";
              }
              else if (hit.metaValueExists("end"))
              {
                e += " end=\"" + String( int(hit.getMetaValue("end")) + 1) + "\"";
              }
              else
              {
                OPENMS_LOG_WARN << "Found no end position of peptide hit in protein sequence." << std::endl;
              }
              if (!idec.empty())
              {
                e += " isDecoy=\"" + String(idec)+ "\"";
              }
              e += "/>\n";
              sen_set.insert(e);
              pevid_ids.push_back(pevid);
            }
            pep_evis.insert(make_pair(pepi, pevid_ids));
          }
          else
          {
            pevid_ids =  pep_evis[pepi];
          }
  
          String cmz((hit.getSequence().getMonoWeight() +  hit.getCharge() * Constants::PROTON_MASS_U) / hit.getCharge()); //calculatedMassToCharge
          String r(hit.getRank()); //rank
          String sc(hit.getScore());
  
          if (sc.empty())
          {
            sc = "NA";
            OPENMS_LOG_WARN << "No score assigned to this PSM: " /*<< hit.getSequence().toString()*/ << std::endl;
          }
          String c(hit.getCharge()); //charge
  
          String pte;
          if (hit.metaValueExists("pass_threshold"))
          {
            pte = boost::lexical_cast<std::string>(hit.getMetaValue("pass_threshold"));
          }
          else if (pp_identifier_2_thresh.find(it->getIdentifier())!= pp_identifier_2_thresh.end() && pp_identifier_2_thresh.find(it->getIdentifier())->second != 0.0)
          {
            double th = pp_identifier_2_thresh.find(it->getIdentifier())->second;
            //threshold was 'set' in proteinIdentification (!= default value of member, now check pass
            pte = boost::lexical_cast<std::string>(it->isHigherScoreBetter() ? hit.getScore() > th : hit.getScore() < th); //passThreshold-eval
          }
          else
          {
            pte = true;
          }
  
          //write SpectrumIdentificationItem elements
          String emz(it->getMZ());
          String sii = "SII_" + String(UniqueIdGenerator::getUniqueId());
          String sii_tmp;
          sii_tmp += String("\t\t\t\t<SpectrumIdentificationItem passThreshold=\"")
                  + pte + String("\" rank=\"") + r + String("\" peptide_ref=\"")
                  + pepid + String("\" calculatedMassToCharge=\"") + cmz
                  + String("\" experimentalMassToCharge=\"") + emz
                  + String("\" chargeState=\"") + c +  String("\" id=\"")
                  + sii + String("\">\n");
  
          if (pevid_ids.empty())
          {
            OPENMS_LOG_WARN << "PSM without peptide evidence registered in the given search database found. This will cause an invalid mzIdentML file (which OpenMS can still consume)." << std::endl;
          }
          for (std::vector<String>::const_iterator pevref = pevid_ids.begin(); pevref != pevid_ids.end(); ++pevref)
          {
            sii_tmp += "\t\t\t\t\t<PeptideEvidenceRef peptideEvidence_ref=\"" +  String(*pevref) + "\"/>\n";
          }
  
          if (! hit.getPeakAnnotations().empty())
          {
            writeFragmentAnnotations_(sii_tmp, hit.getPeakAnnotations(), 5, false);
          }
  
          std::set<String> peptide_result_details;
          cv_.getAllChildTerms(peptide_result_details, "MS:1001143"); // search engine specific score for PSMs
          MetaInfoInterface copy_hit = hit;
          String st(it->getScoreType()); //scoretype
  
          if (cv_.hasTermWithName(st) && peptide_result_details.find(cv_.getTermByName(st).id) != peptide_result_details.end())
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName(st).toXMLString(cv_ns, sc);
            copy_hit.removeMetaValue(cv_.getTermByName(st).id);
          }
          else if (cv_.exists(st) && peptide_result_details.find(st) != peptide_result_details.end())
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTerm(st).toXMLString(cv_ns, sc);
            copy_hit.removeMetaValue(cv_.getTerm(st).id);
          }
          else if (st == "q-value" || st == "FDR")
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName("PSM-level q-value").toXMLString(cv_ns, sc);
            copy_hit.removeMetaValue(cv_.getTermByName("PSM-level q-value").id);
          }
          else if (st == "Posterior Error Probability")
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName("percolator:PEP").toXMLString(cv_ns, sc); // 'percolaror' was not a typo in the code but in the cv.
            copy_hit.removeMetaValue(cv_.getTermByName("percolator:PEP").id);
          }
          else if (st == "OMSSA")
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName("OMSSA:evalue").toXMLString(cv_ns, sc);
            copy_hit.removeMetaValue(cv_.getTermByName("OMSSA:evalue").id);
          }
          else if (st == "Mascot")
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName("Mascot:score").toXMLString(cv_ns, sc);
            copy_hit.removeMetaValue(cv_.getTermByName("Mascot:score").id);
          }
          else if (st == "XTandem")
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName("X\\!Tandem:hyperscore").toXMLString(cv_ns, sc);
            copy_hit.removeMetaValue(cv_.getTermByName("X\\!Tandem:hyperscore").id);
          }
          else if (st == "SEQUEST")
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName("Sequest:xcorr").toXMLString(cv_ns, sc);
            copy_hit.removeMetaValue(cv_.getTermByName("Sequest:xcorr").id);
          }
          else if (st == "MS-GF+")
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName("MS-GF:RawScore").toXMLString(cv_ns, sc);
            copy_hit.removeMetaValue(cv_.getTermByName("MS-GF:RawScore").id);
          }
          else if (st == Constants::UserParam::OPENPEPXL_SCORE)
          {
            sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName(st).toXMLString(cv_ns, sc);
            copy_hit.removeMetaValue(cv_.getTermByName(st).id);
          }
          else
          {
            String score_name_placeholder = st.empty()?"PSM-level search engine specific statistic":st;
            sii_tmp += String(5, '\t') + cv_.getTermByName("PSM-level search engine specific statistic").toXMLString(cv_ns);
            sii_tmp += "\n" + String(5, '\t') + "<userParam name=\"" + score_name_placeholder
                         + "\" unitName=\"" + "xsd:double" + "\" value=\"" + sc + "\"/>";
            OPENMS_LOG_WARN << "Converting unknown score type to PSM-level search engine specific statistic from PSI controlled vocabulary." << std::endl;
          }
          sii_tmp += "\n";
  
          copy_hit.removeMetaValue("calcMZ");
          copy_hit.removeMetaValue(Constants::UserParam::TARGET_DECOY);
          writeMetaInfos_(sii_tmp, copy_hit, 5);
  
          //~ sidres += "<cvParam accession=\"MS:1000796\" cvRef=\"PSI-MS\" value=\"55.835.842.3.dta\" name=\"spectrum title\"/>";
          sii_tmp += "\t\t\t\t</SpectrumIdentificationItem>\n";
          sidres += sii_tmp;
      }
  
      void MzIdentMLHandler::writeXLMSPeptideHit(const PeptideHit& hit,
                                                  std::vector<PeptideIdentification>::const_iterator& it,
                                                  String ppxl_linkid, std::map<String, String>& pep_ids,
                                                  String cv_ns, std::set<String>& sen_set,
                                                  std::map<String, String>& sen_ids,
                                                  std::map<String, std::vector<String> >& pep_evis,
                                                  std::map<String, double>& pp_identifier_2_thresh,
                                                  double ppxl_crosslink_mass,
                                                  std::map<String, String>& ppxl_specref_2_element,
                                                  String& sid, bool alpha_peptide)
      {
  
        String pepid =  "PEP_" + String(UniqueIdGenerator::getUniqueId());
  
        AASequence peptide_sequence;
        if (alpha_peptide)
        {
          peptide_sequence = hit.getSequence();
        }
        else
        {
          peptide_sequence = AASequence::fromString(hit.getMetaValue(Constants::UserParam::OPENPEPXL_BETA_SEQUENCE));
        }
        String pepi = peptide_sequence.toString();
  
        // The same peptide sequence (including mods and link position) can be used several times in different pairs
        // make pepi unique enough, so that PeptideEvidences are written for each case
  
        if (alpha_peptide)
        {
          pepi += "_MS:1002509";
        }
        else
        {
          pepi += "_MS:1002510";
        }
        if (hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TYPE) != "mono-link")  //sequence may contain more than one linker anchors; also code position linked
        {
          if (alpha_peptide)
          {
            pepi += "_" + hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_POS1).toString();
          }
          else
          {
            pepi += "_" + hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_POS2).toString();
          }
        }
        pepi += ppxl_linkid;
  
        bool duplicate = false;
        std::map<String, String>::iterator pit;
        // avoid duplicates in case with only one peptide
        if (hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TYPE) != "cross-link")
        {
          pit = pep_ids.find(pepi);
          if (pit != pep_ids.end())
          {
            duplicate = true;
          }
        }
  
        // TODO another criterion for ppxl: the same "donor" pep_id can only be reused in combination with the same "acceptor" pep_id,
        // for now I will just make an exemption for ppxl data, otherwise we get an invalid file as we have missing peptide pairings.
        // the redundancy should not increase too much, since pairings between exactly the same peptides for different spectra
        // should be a minority
  
        // avoid duplicate pairs in ppxl data
        // TODO access to pep_ids for both peptides from each pair necessary for PSMs
        // below code does not work at all yet
  
        // if (hit.metaValueExists("xl_chain") && it->getHits().size() == 2)
        //          {
        //            std::vector<PeptideHit> peps = it->getHits();
        //            String pepi2 = peps[1].getSequence().toString();
        //            std::map<String, String>::iterator pit = pep_pairs_ppxl.find(pepi);
  
        //            // last entry in vector
        //            std::pair<String, String> pepid_pairs_ppxl[pepid_pairs_ppxl.size()-1];
  
        //            if (pit != pep_pairs_ppxl.end())
        //            {
        //              if (pit->second == pep2)
        //              {
        //                duplicate = true;
        //              }
        //            }
        //          }
  
        if (!duplicate)
        {
          String p;
          //~ TODO simplify mod cv param write
          // write peptide id with conversion to universal, "human-readable" bracket string notation
          p += String("\t<Peptide id=\"") + pepid + String("\" name=\"") +
                peptide_sequence.toBracketString(false) + String("\">\n\t\t<PeptideSequence>") + peptide_sequence.toUnmodifiedString() + String("</PeptideSequence>\n");
  
          const ResidueModification* n_term_mod = peptide_sequence.getNTerminalModification();
          if (n_term_mod != nullptr)
          {
            p += "\t\t<Modification location=\"0\">\n";
            String acc = n_term_mod->getUniModAccession();
            bool unimod = true;
            if (!acc.empty())
            {
              p += "\t\t\t<cvParam accession=\"UNIMOD:" + acc.suffix(':');
            }
            else
            {
              acc = n_term_mod->getPSIMODAccession();
              p += "\t\t\t<cvParam accession=\"XLMOD:" + acc.suffix(':');
              unimod = false;
            }
            p += "\" name=\"" + n_term_mod->getId();
            if (unimod)
            {
              p += "\" cvRef=\"UNIMOD\"/>";
            }
            else
            {
              p += "\" cvRef=\"XLMOD\"/>";
            }
            p += "\n\t\t</Modification>\n";
          }
          const ResidueModification* c_term_mod = peptide_sequence.getCTerminalModification();
          if (c_term_mod != nullptr)
          {
            p += "\t\t<Modification location=\"" + String(peptide_sequence.size()) + "\">\n";
            String acc = c_term_mod->getUniModAccession();
            bool unimod = true;
            if (!acc.empty())
            {
              p += "\t\t\t<cvParam accession=\"UNIMOD:" + acc.suffix(':');
            }
            else
            {
              acc = c_term_mod->getPSIMODAccession();
              p += "\t\t\t<cvParam accession=\"XLMOD:" + acc.suffix(':');
              unimod = false;
            }
            p += "\" name=\"" + c_term_mod->getId();
            if (unimod)
            {
              p += "\" cvRef=\"UNIMOD\"/>";
            }
            else
            {
              p += "\" cvRef=\"XLMOD\"/>";
            }
            p += "\n\t\t</Modification>\n";
          }
          for (Size i = 0; i < peptide_sequence.size(); ++i)
          {
            const ResidueModification* mod = peptide_sequence[i].getModification(); // "UNIMOD:" prefix??
            if (mod != nullptr)
            {
              p += "\t\t<Modification location=\"" + String(i + 1);
              p += "\" residues=\"" + peptide_sequence[i].getOneLetterCode();
              String acc = mod->getUniModAccession();
              if (!acc.empty())
              {
                p += "\">\n\t\t\t<cvParam accession=\"UNIMOD:" + acc.suffix(':'); //TODO @all: do not exclusively use unimod ...
                p += "\" name=\"" + mod->getId();
                p += "\" cvRef=\"UNIMOD\"/>";
                p += "\n\t\t</Modification>\n";
              }
              else
              {
                acc = mod->getPSIMODAccession();
                if (!acc.empty())
                {
                  p += "\">\n\t\t\t<cvParam accession=\"XLMOD:" + acc.suffix(':');
                  p += "\" name=\"" +  mod->getId();
                  p += "\" cvRef=\"XLMOD\"/>";
                  p += "\n\t\t</Modification>\n";
                }
                else
                {
                  // We have an unknown modification, so lets write unknown
                  // and at least try to write down the delta mass.
                  if (mod->getDiffMonoMass() != 0.0)
                  {
                    double diffmass = mod->getDiffMonoMass();
                    p += "\" monoisotopicMassDelta=\"" + String(diffmass);
                  }
                  else if (mod->getMonoMass() > 0.0)
                  {
                    double diffmass = mod->getMonoMass() - peptide_sequence[i].getMonoWeight();
                    p += "\" monoisotopicMassDelta=\"" + String(diffmass);
                  }
                  p += "\">\n\t\t\t<cvParam cvRef=\"MS\" accession=\"MS:1001460\" name=\"unknown modification\"/>";
                  p += "\n\t\t</Modification>\n";
                }
              }
            }
            // Failsafe, if someone uses a new cross-linker (given these conditions, there MUST be a linker at this position, but it does not have a Unimod or XLMOD entry)
            else if (alpha_peptide && hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_MOD) && (static_cast<Size>(hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_POS1).toString().toInt()) == i) && (hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TYPE).toString() == "mono-link") )
            {
              p += "\t\t<Modification location=\"" + String(i + 1);
              p += "\" residues=\"" + String(peptide_sequence[i].getOneLetterCode());
              p += "\">\n\t\t\t<cvParam accession=\"XLMOD:XXXXX";
              p += "\" name=\"" +  hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_MOD).toString();
              p += "\" cvRef=\"XLMOD\"/>";
              p += "\n\t\t</Modification>\n";
            }
          }
  
          if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_TYPE) && hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TYPE) != "mono-link")
          {
            int i = hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_POS1).toString().toInt();
            if (alpha_peptide)
            {
              CrossLinksDB* xl_db = CrossLinksDB::getInstance();
              std::vector<String> mods;
              if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_ALPHA) && hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_ALPHA) == "N_TERM")
              {
                xl_db->searchModificationsByDiffMonoMass(mods, double(hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_MASS)), 0.0001, "", ResidueModification::N_TERM);
                if (mods.size() > 0)
                {
                  p += "\t\t<Modification location=\"0";
                }
              }
              else if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_ALPHA) && hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_ALPHA) == "C_TERM")
              {
                xl_db->searchModificationsByDiffMonoMass(mods, double(hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_MASS)), 0.0001, "", ResidueModification::C_TERM);
                if (mods.size() > 0)
                {
                  p += "\t\t<Modification location=\"" + String(i + 2);
                }
              }
              else
              {
                xl_db->searchModificationsByDiffMonoMass(mods, double(hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_MASS)), 0.0001, String(hit.getSequence()[i].getOneLetterCode()), ResidueModification::ANYWHERE);
                if (mods.size() > 0)
                {
                  p += "\t\t<Modification location=\"" + String(i + 1);
                }
              }
  
              String acc = "";
              String name = "";
              for (Size s = 0; s < mods.size(); ++s)
              {
                if (mods[s].hasSubstring(hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_MOD)))
                {
                  const ResidueModification* mod = nullptr;
                  try
                  {
                    mod = xl_db->getModification(mods[s], peptide_sequence[i].getOneLetterCode(), ResidueModification::ANYWHERE);
                  }
                  catch (...)
                  {
                    if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_ALPHA) && hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_ALPHA) == "N_TERM")
                    {
                      mod = xl_db->getModification(mods[s], "", ResidueModification::N_TERM);
                    }
                    else if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_ALPHA) && hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_ALPHA) == "C_TERM")
                    {
                      mod = xl_db->getModification(mods[s], "", ResidueModification::C_TERM);
                    }
                  }
                  // mod should never be null, but gcc complains (-Werror=maybe-uninitialized)
                  if (mod != nullptr) acc = mod->getPSIMODAccession();
                  if (mod != nullptr) name = mod->getId();
                }
                if (!acc.empty())
                {
                  break;
                }
              }
              if ( acc.empty() && (mods.size() > 0) ) // If ambiguity can not be resolved by xl_mod, just take one with the same mass diff from the database
              {
                const ResidueModification* mod = xl_db->getModification( String(peptide_sequence[i].getOneLetterCode()), mods[0], ResidueModification::ANYWHERE);
                acc = mod->getPSIMODAccession();
                name = mod->getId();
              }
              if (!acc.empty())
              {
                p += "\" residues=\"" + String(peptide_sequence[i].getOneLetterCode());
                p += "\" monoisotopicMassDelta=\"" + hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_MASS).toString() + "\">\n";
                p += "\t\t\t<cvParam accession=\"" + acc + "\" cvRef=\"XLMOD\" name=\"" + name + "\"/>\n";
              }
              else // if there is no matching modification in the database, write out a placeholder
              {
                p += "\t\t<Modification location=\"" + String(i + 1);
                p += "\" residues=\"" + String(peptide_sequence[i].getOneLetterCode());
                p += "\" monoisotopicMassDelta=\"" + hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_MASS).toString() + "\">\n";
                p += "\t\t\t<cvParam accession=\"XLMOD:XXXXX\" cvRef=\"XLMOD\" name=\"" + hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_MOD).toString() + "\"/>\n";
              }
            }
            else // xl_chain = "MS:1002510", acceptor, beta peptide
            {
              i = hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_POS2).toString().toInt();
              if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_BETA) && hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_BETA) == "N_TERM")
              {
                p += "\t\t<Modification location=\"0";
              }
              else if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_BETA) && hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TERM_SPEC_BETA) == "C_TERM")
              {
                p += "\t\t<Modification location=\"" + String(peptide_sequence.size() + 2);
              }
              else
              {
                p += "\t\t<Modification location=\"" + String(i + 1);
              }
              p += "\" residues=\"" + String(peptide_sequence[i].getOneLetterCode());
              p += "\" monoisotopicMassDelta=\"0\">\n";
            }
            if (alpha_peptide)
            {
              p += "\t\t\t" + cv_.getTerm(String("MS:1002509")).toXMLString(cv_ns, DataValue(ppxl_linkid));
            }
            else
            {
              p += "\t\t\t" + cv_.getTerm(String("MS:1002510")).toXMLString(cv_ns, DataValue(ppxl_linkid));
            }
            p += "\n\t\t</Modification>\n";
          }
          if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_TYPE) && hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TYPE) == "loop-link")
          {
            int i = hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_POS2).toString().toInt();
            p += "\t\t<Modification location=\"" + String(i + 1);
            p += "\" residues=\"" + String(peptide_sequence[i].getOneLetterCode());
            p += "\" monoisotopicMassDelta=\"0";
            // ppxl crosslink loop xl_pos2 is always the acceptor ("MS:1002510")
            p += "\">\n\t\t\t" + cv_.getTerm("MS:1002510").toXMLString(cv_ns, DataValue(ppxl_linkid));
            p += "\n\t\t</Modification>\n";
          }
          p += "\t</Peptide>\n ";
          sen_set.insert(p);
          pep_ids.insert(std::make_pair(pepi, pepid));
        }
        else
        {
          pepid = pit->second;
        }
  
        std::vector<String> pevid_ids;
        if (!duplicate)
        {
          if (alpha_peptide)
          {
            std::vector<PeptideEvidence> peptide_evidences = hit.getPeptideEvidences();
            // TODO idXML allows peptide hits without protein references! Fails in that case - run PeptideIndexer first
  
            // TODO BETA PEPTIDE Protein Info
            for (std::vector<PeptideEvidence>::const_iterator pe = peptide_evidences.begin(); pe != peptide_evidences.end(); ++pe)
            {
              String pevid =  "PEV_" + String(UniqueIdGenerator::getUniqueId());
              String dBSequence_ref;
              map<String, String>::const_iterator pos = sen_ids.find(pe->getProteinAccession());
              if (pos != sen_ids.end())
              {
                dBSequence_ref = pos->second;
              }
              else
              {
                OPENMS_LOG_ERROR << "Error: Missing or invalid protein reference for peptide '" << pepi << "': '" << pe->getProteinAccession() << "' - skipping." << endl;
                continue;
              }
              String idec;
              if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_TARGET_DECOY_ALPHA))
              {
                idec = String(boost::lexical_cast<std::string>((String(hit.getMetaValue(Constants::UserParam::OPENPEPXL_TARGET_DECOY_ALPHA))).hasSubstring("decoy")));
              }
  
              String e;
              String nc_termini = "-";    // character for N- and C-termini as specified in mzIdentML
              e += "\t<PeptideEvidence id=\"" + pevid + "\" peptide_ref=\"" + pepid + "\" dBSequence_ref=\"" + dBSequence_ref + "\"";
  
              if (pe->getAAAfter() != PeptideEvidence::UNKNOWN_AA)
              {
                e += " post=\"" + (pe->getAAAfter() == PeptideEvidence::C_TERMINAL_AA ? nc_termini : String(pe->getAAAfter())) + "\"";
              }
              if (pe->getAABefore() != PeptideEvidence::UNKNOWN_AA)
              {
                e += " pre=\"" + (pe->getAABefore() == PeptideEvidence::N_TERMINAL_AA ? nc_termini : String(pe->getAABefore())) + "\"";
              }
              if (pe->getStart() != PeptideEvidence::UNKNOWN_POSITION)
              {
                e += " start=\"" + String(pe->getStart() + 1) + "\"";
              }
              else if (hit.metaValueExists("start"))
              {
                e += " start=\"" + String( int(hit.getMetaValue("start")) + 1) + "\"";
              }
              else
              {
                OPENMS_LOG_WARN << "Found no start position of peptide hit in protein sequence." << std::endl;
              }
              if (pe->getEnd() != PeptideEvidence::UNKNOWN_POSITION)
              {
                e += " end=\"" + String(pe->getEnd() + 1) + "\"";
              }
              else if (hit.metaValueExists("end"))
              {
                e += " end=\"" + String( int(hit.getMetaValue("end")) + 1) + "\"";
              }
              else
              {
                OPENMS_LOG_WARN << "Found no end position of peptide hit in protein sequence." << std::endl;
              }
              if (!idec.empty())
              {
                e += " isDecoy=\"" + String(idec)+ "\"";
              }
              e += "/>\n";
              sen_set.insert(e);
              pevid_ids.push_back(pevid);
            }
            pep_evis.insert(make_pair(pepi, pevid_ids));
          }
          else // acceptor, beta peptide, does not have its own PeptideHit and PeptideEvidences
          {
            StringList prot = ListUtils::create<String>(String(hit.getMetaValue(Constants::UserParam::OPENPEPXL_BETA_ACCESSIONS)));
            StringList pre = ListUtils::create<String>(String(hit.getMetaValue(Constants::UserParam::OPENPEPXL_BETA_PEPEV_PRE)));
            StringList post = ListUtils::create<String>(String(hit.getMetaValue(Constants::UserParam::OPENPEPXL_BETA_PEPEV_POST)));
            StringList start = ListUtils::create<String>(String(hit.getMetaValue(Constants::UserParam::OPENPEPXL_BETA_PEPEV_START)));
            StringList end = ListUtils::create<String>(String(hit.getMetaValue(Constants::UserParam::OPENPEPXL_BETA_PEPEV_END)));
            for (Size ev = 0; ev < pre.size(); ++ev)
            {
              String pevid =  "PEV_" + String(UniqueIdGenerator::getUniqueId());
              String dBSequence_ref;
              map<String, String>::const_iterator pos = sen_ids.find(prot[ev]);
              if (pos != sen_ids.end())
              {
                dBSequence_ref = pos->second;
              }
              else
              {
                OPENMS_LOG_ERROR << "Error: Missing or invalid protein reference for peptide '" << pepi << "': '" << prot[ev] << "' - skipping." << endl;
                continue;
              }
              String idec;
              if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_TARGET_DECOY_BETA))
              {
                idec = String(boost::lexical_cast<std::string>((String(hit.getMetaValue(Constants::UserParam::OPENPEPXL_TARGET_DECOY_BETA))).hasSubstring("decoy")));
              }
  
              String e;
              String nc_termini = "-";    // character for N- and C-termini as specified in mzIdentML
              e += "\t<PeptideEvidence id=\"" + pevid + "\" peptide_ref=\"" + pepid + "\" dBSequence_ref=\"" + dBSequence_ref + "\"";
  
              if (post[ev] != String(PeptideEvidence::UNKNOWN_AA))
              {
                e += " post=\"" + (post[ev] == String(PeptideEvidence::C_TERMINAL_AA) ? nc_termini : post[ev]) + "\"";
              }
              if (pre[ev] != String(PeptideEvidence::UNKNOWN_AA))
              {
                e += " pre=\"" + (pre[ev] == String(PeptideEvidence::N_TERMINAL_AA) ? nc_termini : pre[ev]) + "\"";
              }
              if (start[ev] != String(PeptideEvidence::UNKNOWN_POSITION))
              {
                e += " start=\"" + String(start[ev].toInt() + 1) + "\"";
              }
              else
              {
                OPENMS_LOG_WARN << "Found no start position of peptide hit in protein sequence." << std::endl;
              }
              if (end[ev] != String(PeptideEvidence::UNKNOWN_POSITION))
              {
                e += " end=\"" + String(end[ev].toInt() + 1) + "\"";
              }
              else
              {
                OPENMS_LOG_WARN << "Found no end position of peptide hit in protein sequence." << std::endl;
              }
              if (!idec.empty())
              {
                e += " isDecoy=\"" + String(idec)+ "\"";
              }
              e += "/>\n";
              sen_set.insert(e);
              pevid_ids.push_back(pevid);
            }
            pep_evis.insert(make_pair(pepi, pevid_ids));
          }
        }
        else
        {
          pevid_ids =  pep_evis[pepi];
        }
  
        String r(hit.getRank()); //rank
        String sc(hit.getScore());
        if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_RANK))
        {
          r = hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_RANK).toString();  // ppxl remove xl_rank later (in copy_hit)
        }
  
        //Calculated mass to charge for cross-linked is both peptides + linker
        double calc_ppxl_mass = hit.getSequence().getMonoWeight();
        if (hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TYPE) == "cross-link")
        {
          calc_ppxl_mass += ppxl_crosslink_mass + AASequence::fromString(hit.getMetaValue(Constants::UserParam::OPENPEPXL_BETA_SEQUENCE)).getMonoWeight();
        }
        // if xl_mod and xl_mass MetaValues exist, then the mass of the mono-link could not be set as a AASequence modification and will not be considered by .getMonoWeight
        else if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_MOD) && hit.metaValueExists(Constants::UserParam::OPENPEPXL_XL_MASS))
        {
          calc_ppxl_mass += double(hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_MASS));
        }
        String cmz = String( ((calc_ppxl_mass) +  (hit.getCharge() * Constants::PROTON_MASS_U)) / hit.getCharge()); //calculatedMassToCharge
  
        if (sc.empty())
        {
          sc = "NA";
          OPENMS_LOG_WARN << "No score assigned to this PSM: " /*<< hit.getSequence().toString()*/ << std::endl;
        }
        String c(hit.getCharge()); //charge
  
        String pte;
        if (hit.metaValueExists("pass_threshold"))
        {
          pte = boost::lexical_cast<std::string>(hit.getMetaValue("pass_threshold"));
        }
        else if (pp_identifier_2_thresh.find(it->getIdentifier())!= pp_identifier_2_thresh.end() && pp_identifier_2_thresh.find(it->getIdentifier())->second != 0.0)
        {
          double th = pp_identifier_2_thresh.find(it->getIdentifier())->second;
          //threshold was 'set' in proteinIdentification (!= default value of member, now check pass
          pte = boost::lexical_cast<std::string>(it->isHigherScoreBetter() ? hit.getScore() > th : hit.getScore() < th); //passThreshold-eval
        }
        else
        {
          pte = true;
        }
  
        //write SpectrumIdentificationItem elements
        String emz(it->getMZ());
        String sii = "SII_" + String(UniqueIdGenerator::getUniqueId());
        String sii_tmp;
        sii_tmp += String("\t\t\t\t<SpectrumIdentificationItem passThreshold=\"")
                + pte + String("\" rank=\"") + r + String("\" peptide_ref=\"")
                + pepid + String("\" calculatedMassToCharge=\"") + cmz
                + String("\" experimentalMassToCharge=\"") + emz
                + String("\" chargeState=\"") + c +  String("\" id=\"")
                + sii + String("\">\n");
  
        if (pevid_ids.empty())
        {
          OPENMS_LOG_WARN << "PSM without peptide evidence registered in the given search database found. This will cause an invalid mzIdentML file (which OpenMS can still consume)." << std::endl;
        }
        for (std::vector<String>::const_iterator pevref = pevid_ids.begin(); pevref != pevid_ids.end(); ++pevref)
        {
          sii_tmp += "\t\t\t\t\t<PeptideEvidenceRef peptideEvidence_ref=\"" +  String(*pevref) + "\"/>\n";
        }
  
        if (! hit.getPeakAnnotations().empty() && alpha_peptide)
        {
          // is_ppxl = true
          writeFragmentAnnotations_(sii_tmp, hit.getPeakAnnotations(), 5, true);
        }
  
        std::set<String> peptide_result_details;
        cv_.getAllChildTerms(peptide_result_details, "MS:1001143"); // search engine specific score for PSMs
        MetaInfoInterface copy_hit = hit;
        String st(it->getScoreType()); //scoretype
  
        if (cv_.hasTermWithName(st) && peptide_result_details.find(cv_.getTermByName(st).id) != peptide_result_details.end())
        {
          sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName(st).toXMLString(cv_ns, sc);
          copy_hit.removeMetaValue(cv_.getTermByName(st).id);
        }
        else if (cv_.exists(st) && peptide_result_details.find(st) != peptide_result_details.end())
        {
          sii_tmp +=  "\t\t\t\t\t" + cv_.getTerm(st).toXMLString(cv_ns, sc);
          copy_hit.removeMetaValue(cv_.getTerm(st).id);
        }
        else if (st == "q-value" || st == "FDR")
        {
          sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName("PSM-level q-value").toXMLString(cv_ns, sc);
          copy_hit.removeMetaValue(cv_.getTermByName("PSM-level q-value").id);
        }
        else if (st == "Posterior Error Probability")
        {
          sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName("percolator:PEP").toXMLString(cv_ns, sc); // 'percolaror' was not a typo in the code but in the cv.
          copy_hit.removeMetaValue(cv_.getTermByName("percolator:PEP").id);
        }
        else if (st == Constants::UserParam::OPENPEPXL_SCORE)
        {
          sii_tmp +=  "\t\t\t\t\t" + cv_.getTermByName(st).toXMLString(cv_ns, sc);
          copy_hit.removeMetaValue(cv_.getTermByName(st).id);
        }
        else
        {
          String score_name_placeholder = st.empty()?"PSM-level search engine specific statistic":st;
          sii_tmp += String(5, '\t') + cv_.getTermByName("PSM-level search engine specific statistic").toXMLString(cv_ns);
          sii_tmp += "\n" + String(5, '\t') + "<userParam name=\"" + score_name_placeholder
                       + "\" unitName=\"" + "xsd:double" + "\" value=\"" + sc + "\"/>";
          OPENMS_LOG_WARN << "Converting unknown score type to PSM-level search engine specific statistic from PSI controlled vocabulary." << std::endl;
        }
        sii_tmp += "\n";
  
        copy_hit.removeMetaValue("calcMZ");
        copy_hit.removeMetaValue(Constants::UserParam::TARGET_DECOY);
  
        // TODO this would be the correct way, but need to adjust parsing as well
        if (copy_hit.metaValueExists(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_MZ) || copy_hit.getMetaValue(Constants::UserParam::OPENPEPXL_XL_TYPE) == "cross-link")
        {
          sii_tmp +=  "\t\t\t\t\t" + cv_.getTerm("MS:1002511").toXMLString(cv_ns, ppxl_linkid) + "\n"; // cross-linked spectrum identification item
        }
  
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_XL_RANK);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_XL_POS1);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_XL_POS2);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_XL_MOD);
        copy_hit.removeMetaValue("xl_chain");
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_XL_MASS);
        copy_hit.removeMetaValue("protein_references");
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_REF);
        copy_hit.removeMetaValue(Constants::UserParam::SPECTRUM_REFERENCE);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_MZ);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_RT);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_BETA_PEPEV_PRE);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_BETA_PEPEV_POST);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_BETA_PEPEV_START);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_BETA_PEPEV_END);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_BETA_SEQUENCE);
        copy_hit.removeMetaValue(Constants::UserParam::OPENPEPXL_BETA_ACCESSIONS);
  
        writeMetaInfos_(sii_tmp, copy_hit, 5);
  
        //~ sidres += "<cvParam accession=\"MS:1000796\" cvRef=\"PSI-MS\" value=\"55.835.842.3.dta\" name=\"spectrum title\"/>";
        sii_tmp += "\t\t\t\t</SpectrumIdentificationItem>\n";
  
        const double rt = it->getRT();
        String ert = rt == rt ? String(rt) : "nan";
        DataValue rtcv(ert);
        rtcv.setUnit(10); // id: UO:0000010 name: second
        rtcv.setUnitType(DataValue::UnitType::UNIT_ONTOLOGY);
        sii_tmp = sii_tmp.substitute("</SpectrumIdentificationItem>",
                                     "\t" + cv_.getTermByName("retention time").toXMLString(cv_ns, rtcv) + "\n\t\t\t\t</SpectrumIdentificationItem>\n");
        ppxl_specref_2_element[sid] += sii_tmp;
        if (hit.metaValueExists(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_RT) && hit.metaValueExists(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_MZ))
        {
          // ppxl : TODO remove if existing <Fragmentation/>block
          sii_tmp = sii_tmp.substitute(String("experimentalMassToCharge=\"") + String(emz),
                                       String("experimentalMassToCharge=\"") + String(hit.getMetaValue(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_MZ))); // mz
          sii_tmp = sii_tmp.substitute(sii, String("SII_") + String(UniqueIdGenerator::getUniqueId())); // uid
          sii_tmp = sii_tmp.substitute("value=\"" + ert, "value=\"" + String(hit.getMetaValue(Constants::UserParam::OPENPEPXL_HEAVY_SPEC_RT)));
  
          ProteinIdentification::SearchParameters search_params = cpro_id_->front().getSearchParameters();
          double iso_shift = String(search_params.getMetaValue("cross_link:mass_isoshift")).toDouble();
          double cmz_heavy = cmz.toDouble() + (iso_shift / hit.getCharge());
  
          sii_tmp = sii_tmp.substitute(String("calculatedMassToCharge=\"") + String(cmz),
                                        String("calculatedMassToCharge=\"") + String(cmz_heavy));
  
          ppxl_specref_2_element[sid] += sii_tmp;
        }
      }
    } //namespace Internal
  } // namespace OpenMS
  