Source: src/js/collections/ObjectFormats.js

"use strict";

define([
  "backbone",
  "common/ValueUtilities",
  "x2js",
  "models/formats/ObjectFormat",
], (Backbone, ValueUtilities, X2JS, ObjectFormat) => {
  const FORMAT_IDS = {
    RESOURCE_MAP: "http://www.openarchives.org/ore/terms",
  };

  const DEFAULT_FORMAT_ID = "application/octet-stream";

  const FORMAT_TYPES = Object.freeze({
    RESOURCE: "RESOURCE",
    METADATA: "METADATA",
    DATA: "DATA",
  });

  const FORMAT_TYPE_VALUES = Object.values(FORMAT_TYPES);

  const OBJECT_TYPES = Object.freeze({
    ANNOTATION: "annotation",
    COLLECTION: "collection",
    DATA: "data",
    IMAGE: "image",
    METADATA: "metadata",
    PDF: "PDF",
    PORTAL: "portal",
    PROGRAM: "program",
  });

  const OBJECT_TYPE_FORMAT_IDS = Object.freeze({
    ANNOTATION: Object.freeze([
      "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html",
    ]),
    COLLECTION: Object.freeze([
      "https://purl.dataone.org/collections-1.0.0",
      "https://purl.dataone.org/collections-1.1.0",
    ]),
    IMAGE: Object.freeze([
      "image/gif",
      "image/jp2",
      "image/jpeg",
      "image/png",
      "image/svg xml",
      "image/svg+xml",
      "image/bmp",
    ]),
    PDF: Object.freeze(["application/pdf"]),
    PORTAL: Object.freeze([
      "https://purl.dataone.org/portals-1.0.0",
      "https://purl.dataone.org/portals-1.1.0",
    ]),
  });

  const PROVONE_PROGRAM_CLASS = "#Program";

  const EML_FORMATS = [
    "eml://ecoinformatics.org/*",
    "https://eml.ecoinformatics.org/*",
  ];

  const PREFERRED_EXTENSION_FORMAT_IDS = {
    jpg: "image/jpeg",
    json: "application/json",
    nc: "netCDF-3",
    rdf: "application/rdf+xml",
    tiff: "image/tiff",
    xls: "application/vnd.ms-excel",
    xml: "text/xml",
    zip: "application/zip",
  };

  const FRIENDLY_FORMAT_NAMES = {
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
      "Microsoft Excel OpenXML",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
      "Microsoft Word OpenXML",
    "application/vnd.ms-excel.sheet.binary.macroEnabled.12":
      "Microsoft Office Excel 2007 binary workbooks",
    "application/vnd.openxmlformats-officedocument.presentationml.presentation":
      "Microsoft Office OpenXML Presentation",
    "application/vnd.ms-excel": "Microsoft Excel",
    "application/msword": "Microsoft Word",
    "application/vnd.ms-powerpoint": "Microsoft Powerpoint",
    "text/html": "HTML",
    "text/plain": "plain text (.txt)",
    "text/csv": "CSV",
    "text/comma-separated-values": "CSV",
    "text/tab-separated-values": "TSV",
    "video/avi": "Microsoft AVI file",
    "video/x-ms-wmv": "Windows Media Video (.wmv)",
    "audio/x-ms-wma": "Windows Media Audio (.wma)",
    "application/vnd.google-earth.kml xml":
      "Google Earth Keyhole Markup Language (KML)",
    "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html":
      "annotation",
    "application/mathematica": "Mathematica Notebook",
    "application/postscript": "Postscript",
    "application/rtf": "Rich Text Format (RTF)",
    "application/xml": "XML Application",
    "text/xml": "XML",
    "application/x-fasta": "FASTA sequence file",
    "nexus/1997": "NEXUS File Format for Systematic Information",
    "anvl/erc-v02":
      "Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13",
    "http://purl.org/dryad/terms/":
      "Dryad Metadata Application Profile Version 3.0",
    "http://datadryad.org/profile/v3.1":
      "Dryad Metadata Application Profile Version 3.1",
    "application/pdf": "PDF",
    "application/zip": "ZIP file",
    "http://www.w3.org/TR/rdf-syntax-grammar": "RDF/XML",
    "http://www.w3.org/TR/rdfa-syntax": "RDFa",
    "application/rdf xml": "RDF",
    "text/turtle": "TURTLE",
    "text/n3": "N3",
    "application/x-gzip": "GZIP Format",
    "application/x-python": "Python script",
    "http://www.w3.org/2005/Atom": "ATOM-1.0",
    "application/octet-stream": "octet stream (application file)",
    "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd":
      "Darwin Core, v2.0",
    "http://rs.tdwg.org/dwc/xsd/simpledarwincore/": "Simple Darwin Core",
    "eml://ecoinformatics.org/eml-2.1.0": "EML v2.1.0",
    "eml://ecoinformatics.org/eml-2.1.1": "EML v2.1.1",
    "eml://ecoinformatics.org/eml-2.0.1": "EML v2.0.1",
    "eml://ecoinformatics.org/eml-2.0.0": "EML v2.0.0",
    "https://eml.ecoinformatics.org/eml-2.2.0": "EML v2.2.0",
  };

  // So that the collection can be used immediately. Various processes in
  // MetacatUI need to know about formats before other actions happen, like
  // rendering. This gets updated with the server-provided formats when the
  // collection is fetched.
  const FALLBACK_FORMATS = Object.freeze(
    `eml://ecoinformatics.org/eml-2.0.0|Ecological Metadata Language, version 2.0.0|METADATA|text/xml|xml
eml://ecoinformatics.org/eml-2.0.1|Ecological Metadata Language, version 2.0.1|METADATA|text/xml|xml
eml://ecoinformatics.org/eml-2.1.0|Ecological Metadata Language, version 2.1.0|METADATA|text/xml|xml
eml://ecoinformatics.org/eml-2.1.1|Ecological Metadata Language, version 2.1.1|METADATA|text/xml|xml
https://eml.ecoinformatics.org/eml-2.2.0|Ecological Metadata Language, version 2.2.0|METADATA|text/xml|xml
http://www.esri.com/metadata/esriprof80.dtd|ESRI Profile of the Content Standard for Digital Geospatial Metadata, March 2003|METADATA|text/xml|xml
FGDC-STD-001.1-1999|Content Standard for Digital Geospatial Metadata, Biological Data Profile, version 001.1-1999|METADATA|text/xml|xml
FGDC-STD-001.2-1999|Content Standard for Digital Geospatial Metadata, Metadata Profile for Shoreline Data, version 001.2-1999|METADATA|text/xml|xml
FGDC-STD-001-1998|Content Standard for Digital Geospatial Metadata, version 001-1998|METADATA|text/xml|xml
INCITS-453-2009|North American Profile of ISO 19115: 2003 Geographic Information - Metadata|METADATA|text/xml|xml
http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2|NetCDF Markup Language, version 2.2|METADATA|application/ncML+xml|ncml
CF-1.0|NetCDF Climate and Forecast Metadata Convention, version 1.0|DATA|application/netcdf|nc
CF-1.1|NetCDF Climate and Forecast Metadata Convention, version 1.1|DATA|application/netcdf|nc
CF-1.2|NetCDF Climate and Forecast Metadata Convention, version 1.2|DATA|application/netcdf|nc
CF-1.3|NetCDF Climate and Forecast Metadata Convention, version 1.3|DATA|application/netcdf|nc
CF-1.4|NetCDF Climate and Forecast Metadata Convention, version 1.4|DATA|application/netcdf|nc
CF-1.5|NetCDF Climate and Forecast Metadata Convention, version 1.5|DATA|application/netcdf|nc
CF-1.6|NetCDF Climate and Forecast Metadata Convention, version 1.6|DATA|application/netcdf|nc
CF-1.7|NetCDF Climate and Forecast Metadata Convention, version 1.7|DATA|application/netcdf|nc
netCDF-CF|NetCDF Climate and Forecast Metadata Convention|DATA|application/netcdf|nc
http://www.cuahsi.org/waterML/1.0/|Water Markup Language, version 1.0|DATA|text/xml|xml
http://www.cuahsi.org/waterML/1.1/|Water Markup Language, version 1.1|DATA|text/xml|xml
http://www.loc.gov/METS/|Metadata Encoding and Transmission Standard, version 1|METADATA|text/xml|xml
netCDF-3|Network Common Data Format, version 3|DATA|application/netcdf|nc
netCDF-4|Network Common Data Format, version 4|DATA|application/netcdf|nc
text/plain|Plain Text|DATA|text/plain|txt
text/csv|Comma Separated Values Text|DATA|text/csv|csv
image/bmp|Bitmap Image File|DATA|image/bmp|bmp
image/gif|Graphics Interchange Format|DATA|image/gif|gif
image/jp2|JPEG 2000|DATA|image/jp2|jpg
image/jpeg|JPEG|DATA|image/jpeg|jpg
image/png|Portable Network Graphics|DATA|image/png|png
image/svg+xml|Scalable Vector Graphics|DATA|image/svg+xml|svg
image/tiff|Tagged Image File Format|DATA|image/tiff|tiff
http://rs.tdwg.org/dwc/xsd/simpledarwincore/|Simple Darwin Core|METADATA|text/xml|xml
http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd|Darwin Core, version 2.0|METADATA|text/xml|xml
application/octet-stream|Octet Stream|DATA|application/octet-stream|data
http://www.w3.org/2005/Atom|ATOM-1.0|DATA|application/atom+xml|atom
text/n3|N3|DATA|text/n3|rdf
text/turtle|TURTLE|DATA|text/turtle|ttl
application/rdf+xml|Resource Description Framework|DATA|application/rdf+xml|rdf
http://www.w3.org/TR/rdf-testcases/#ntriples|N-TRIPLE|DATA|text/plain|nt
http://www.w3.org/TR/rdf-syntax-grammar|RDF/XML|DATA|application/rdf+xml|rdf
http://www.w3.org/TR/rdfa-syntax|RDFa|DATA|application/xhtml+xml|xhtml
http://www.openarchives.org/ore/terms|Object Reuse and Exchange Vocabulary|RESOURCE|application/rdf+xml|rdf
application/pdf|Portable Document Format|DATA|application/pdf|pdf
http://datadryad.org/profile/v3.1|Dryad Metadata Application Profile Version 3.1|METADATA|text/xml|xml
http://purl.org/dryad/terms/|Dryad Metadata Application Profile Version 3.0|METADATA|text/xml|xml
application/vnd.ms-excel|Microsoft Excel file format|DATA|application/vnd.ms-excel|xls
application/msword|Microsoft Word file format|DATA|application/msword|doc
anvl/erc-v02|Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13|DATA|text/anvl|anvl
nexus/1997|NEXUS File Format for Systematic Information|DATA|text/plain|nxs
application/zip|Zip file format|DATA|application/zip|zip
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet|Microsoft Excel OpenXML|DATA|application/vnd.openxmlformats-officedocument.spreadsheetml.sheet|xlsx
application/vnd.openxmlformats-officedocument.wordprocessingml.document|Microsoft Word OpenXML|DATA|application/vnd.openxmlformats-officedocument.wordprocessingml.document|docx
application/x-fasta|FASTA sequence file|DATA|application/x-fasta|fasta
text/xml|Extensible Markup Language|DATA|text/xml|xml
application/xml|Extensible Markup Language Application|DATA|application/xml|xml
application/rtf|Rich Text Format|DATA|application/rtf|rtf
text/html|Hypertext Markup Language|DATA|text/html|html
application/postscript|Postscript|DATA|application/postscript|ps
audio/x-wav|Wave Audio Format|DATA|audio/x-wav|wav
application/mathematica|Mathematica Notebook|DATA|application/mathematica|nb
video/quicktime|Quicktime Video|DATA|video/quicktime|mov
application/x-gzip|GZIP Format|DATA|application/x-gzip|gz
application/x-python|Python programming language script|DATA|text/x-python|py
video/x-ms-wmv|Windows Media Video File|DATA|video/x-ms-wmv|wmv
audio/x-ms-wma|Windows Media Audio File|DATA|audio/x-ms-wma|wma
video/avi|Microsoft Audio Video Interleave (AVI) File|DATA|video/avi|avi
video/mpeg|MPEG-1 Video|DATA|video/mpeg|mpg
audio/mpeg|MPEG-1 or MPEG-2 Audio Layer III|DATA|audio/mpeg|mp3
video/mp4|MPEG-4 Video|DATA|video/mp4|mp4
application/x-tar|Tape Archive File|DATA|application/x-tar|tar
application/x-bzip2|Bzip2 Compressed File|DATA|application/x-bzip2|zip
application/vnd.google-earth.kml+xml|Google Earth Keyhole Markup Language (KML)|DATA|application/vnd.google-earth.kml+xml|kml
application/x-rar-compressed|Roshal Archive File|DATA|application/x-rar-compressed|rar
application/vnd.ms-excel.sheet.binary.macroEnabled.12|Microsoft Office Excel 2007 binary workbooks|DATA|application/vnd.ms-excel.sheet.binary.macroEnabled.12|xls
application/vnd.ms-powerpoint|Microsoft Office Powerpoint|DATA|application/vnd.ms-powerpoint|ppt
application/vnd.openxmlformats-officedocument.presentationml.presentation|Microsoft Office OpenXML Presentation|DATA|application/vnd.openxmlformats-officedocument.presentationml.presentation|pptx
-//ecoinformatics.org//eml-access-2.0.0beta4//EN|Ecological Metadata Language, Access module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-attribute-2.0.0beta4//EN|Ecological Metadata Language, Attribute module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-constraint-2.0.0beta4//EN|Ecological Metadata Language, Constraint module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-coverage-2.0.0beta4//EN|Ecological Metadata Language, Coverage module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-dataset-2.0.0beta4//EN|Ecological Metadata Language, Dataset module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-distribution-2.0.0beta4//EN|Ecological Metadata Language, Distribution module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-entity-2.0.0beta4//EN|Ecological Metadata Language, Entity module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-literature-2.0.0beta4//EN|Ecological Metadata Language, Literature module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-party-2.0.0beta4//EN|Ecological Metadata Language, Party module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-physical-2.0.0beta4//EN|Ecological Metadata Language, Physical module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-project-2.0.0beta4//EN|Ecological Metadata Language, Project module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-protocol-2.0.0beta4//EN|Ecological Metadata Language, Protocol module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-resource-2.0.0beta4//EN|Ecological Metadata Language, Resource module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-software-2.0.0beta4//EN|Ecological Metadata Language, Software module, version 2.0.0beta4|METADATA|text/xml|xml
-//ecoinformatics.org//eml-access-2.0.0beta6//EN|Ecological Metadata Language, Access module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-attribute-2.0.0beta6//EN|Ecological Metadata Language, Attribute module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-constraint-2.0.0beta6//EN|Ecological Metadata Language, Constraint module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-coverage-2.0.0beta6//EN|Ecological Metadata Language, Coverage module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-dataset-2.0.0beta6//EN|Ecological Metadata Language, Dataset module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-distribution-2.0.0beta6//EN|Ecological Metadata Language, Distribution module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-entity-2.0.0beta6//EN|Ecological Metadata Language, Entity module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-literature-2.0.0beta6//EN|Ecological Metadata Language, Literature module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-party-2.0.0beta6//EN|Ecological Metadata Language, Party module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-physical-2.0.0beta6//EN|Ecological Metadata Language, Physical module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-project-2.0.0beta6//EN|Ecological Metadata Language, Project module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-protocol-2.0.0beta6//EN|Ecological Metadata Language, Protocol module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-resource-2.0.0beta6//EN|Ecological Metadata Language, Resource module, version 2.0.0beta6|METADATA|text/xml|xml
-//ecoinformatics.org//eml-software-2.0.0beta6//EN|Ecological Metadata Language, Software module, version 2.0.0beta6|METADATA|text/xml|xml
ddi:codebook:2_5|Data Documentation Initiative, Codebook version 2.5|METADATA|text/xml|xml
http://www.icpsr.umich.edu/DDI|Data Documentation Initiative, Codebook version 2.1|METADATA|text/xml|xml
http://purl.org/ornl/schema/mercury/terms/v1.0|Oak Ridge National Lab Mercury Metadata version 1.0|METADATA|text/xml|xml
http://datacite.org/schema/kernel-3.0|DataCite Metadata Schema version 3.0|METADATA|text/xml|xml
http://datacite.org/schema/kernel-3.1|DataCite Metadata Schema version 3.1|METADATA|text/xml|xml
http://www.nexml.org/2009|NeXML 2009|DATA|text/xml|xml
http://ns.dataone.org/metadata/schema/onedcx/v1.0|DataONE Dublin Core Extended v1.0|METADATA|text/xml|xml
http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html|AnnotatorJS 1.2.x Annotation model|RESOURCE|application/json|json
http://www.isotc211.org/2005/gmd|Geographic MetaData (GMD) Extensible Markup Language|METADATA|text/xml|xml
http://www.isotc211.org/2005/gmd-noaa|NOAA Variant Geographic MetaData (GMD) Extensible Markup Language|METADATA|text/xml|xml
http://www.isotc211.org/2005/gmd-pangaea|PANGAEA Variant Geographic MetaData (GMD) Extensible Markup Language|METADATA|text/xml|xml
application/bagit-097|BagIt File Packaging Format Version 0.97|DATA||
text/tsv|Tab Separated Values Text|DATA|text/tab-separated-values|tsv
application/R|R programming language script|DATA|text/x-rsrc|R
application/MATLAB|MATLAB programming language script|DATA|text/x-matlab|m
application/SAS|SAS programming language script|DATA|text/x-sas|sas
application/vnd.google-earth.kmz|Google Earth Keyhole Markup Language (KML) Compressed archive|DATA|application/vnd.google-earth.kmz|kmz
application/MATLAB-v7.3|Mathworks MATLAB version 7.3 (R2006b or later) binary file - HDF5 compatible|DATA|application/octet-stream|mat
application/MATLAB-v7|Mathworks MATLAB version 7 (R14 or later) binary file|DATA|application/octet-stream|mat
application/MATLAB-v6|Mathworks MATLAB version 6 (R8 or later) binary file|DATA|application/octet-stream|mat
application/MATLAB-v4|Mathworks MATLAB version 4 binary file|DATA|application/octet-stream|mat
application/json|JavaScript Object Notation (JSON) file|DATA|application/json|json
application/json-ld|JavaScript Object Notation (JSON) Linked Data file|DATA|application/ld+json|json
text/markdown|Markdown file|DATA|text/markdown|md
text/x-rmarkdown|R Markdown file|DATA|text/markdown|Rmd
image/x-raw|RAW digital sensor image file|DATA|application/octet-stream|raw
http://www.openarchives.org/OAI/2.0/oai_dc/|OAI-PMH Dublin Core v2.0, with online related resource|METADATA|text/xml|xml
application/x-hdf|Hierarchical Data Format version 4 (HDF4)|DATA|application/x-hdf|h4
application/x-hdf5|Hierarchical Data Format version 5 (HDF5)|DATA|application/x-hdf5|h5
https://purl.dataone.org/collections-1.0.0|Dataset collections v1.0.0|METADATA|text/xml|xml
https://purl.dataone.org/portals-1.0.0|Dataset portals v1.0.0|METADATA|text/xml|xml
https://purl.dataone.org/collections-1.1.0|Dataset collections v1.1.0|METADATA|text/xml|xml
https://purl.dataone.org/portals-1.1.0|Dataset portals v1.1.0|METADATA|text/xml|xml
application/vnd.shp+zip|Esri Shapefile (zipped)|DATA|application/vnd.shp+zip|zip
image/geotiff|GeoTIFF|DATA|image/tiff|tiff
image/geotiff+zip|GeoTIFF (zipped)|DATA|image/tiff+zip|zip
https://doi.org/10.5063/schema/codemeta-1.0|CodeMeta, version 1.0|DATA|application/ld+json|json
https://doi.org/10.5063/schema/codemeta-2.0|CodeMeta, version 2.0|DATA|application/ld+json|json
application/x-sh|Bourne shell script|DATA|application/x-sh|sh
application/x-ipynb+json|Jupyter Notebook|DATA|application/x-ipynb+json|ipynb
science-on-schema.org/Dataset;ld+json|JSON-LD metadata|METADATA|application/ld+json|jsonld
application/geopackage+sqlite3|GeoPackage Encoding Standard (OGC) Format Family|DATA|application/geopackage+sqlite3|gpkg
application/geo+json|GeoJSON|DATA|application/geo+json|json
application/vnd.gdb+zip|Esri File Geodatabase (zipped)|DATA|application/vnd.gdb+zip|zip
application/vnd.apache.parquet|Apache Parquet|DATA|application/vnd.apache.parquet|parquet
application/vnd.sqlite3|SQLite Database|DATA|application/vnd.sqlite3|db
https://ns.dataone.org/service/types/v2.0#SystemMetadata|System Metadata|METADATA|text/xml|xml
http://ns.dataone.org/service/types/v2.0#ObjectFormatList|Object Format List|METADATA|text/xml|xml
application/x-cdf|Common Data Format (CDF)|DATA|application/x-cdf|cdf
application/fits|Flexible Image Transport System (FITS)|DATA|application/fits|fits
Zarr|Zarr|DATA|application/x+zarr|zarr
ASDF|Advanced Scientific Data Format (ASDF)|DATA|application/x-asdf+yaml|asdf
application/gpx+xml|Global Positioning System XML (GPX)|DATA|application/gpx+xml|gpx`
      .trim()
      .split("\n")
      .map((row) => {
        const [formatId, formatName, formatType, mediaType, extension] =
          row.split("|");
        return {
          formatId,
          formatName,
          formatType,
          ...(mediaType ? { mediaType } : {}),
          ...(extension ? { extension } : {}),
        };
      }),
  );

  const FALLBACK_FORMAT_NAMES = Object.fromEntries(
    FALLBACK_FORMATS.map(({ formatId, formatName }) => [formatId, formatName]),
  );

  /**
   * @class ObjectFormats
   * @classdesc ObjectFormats represents the DataONE object format list found at
   * https://cn.dataone.org/cn/v2/formats. This collection starts with built-in
   * fallback definitions and refreshes from an injected formats service when
   * available.
   * @classcategory Collections
   * @augments Backbone.Collection
   * @class
   */
  const ObjectFormats = Backbone.Collection.extend(
    /** @lends ObjectFormats.prototype */ {
      FORMAT_IDS,
      DEFAULT_FORMAT_ID,
      FORMAT_TYPES,
      FORMAT_TYPE_VALUES,
      OBJECT_TYPES,
      EML_FORMATS,
      FALLBACK_FORMATS,

      model: ObjectFormat,

      /**
       * Initialize fallback and remote-fetch state for the collection.
       * @param {object[]|Backbone.Model[]} [models] Initial format models.
       * @param {object} [options] Collection options.
       * @param {boolean} [options.remote] Whether the models are remote
       * formats.
       * @param {boolean} [options.fallback] Set false to skip fallback formats.
       * @param {string} [options.formatsServiceUrl] URL used to fetch formats
       * @since 2.39.0
       */
      initialize(models, options = {}) {
        this.formatsServiceUrl = options.formatsServiceUrl || null;
        this.hasRemoteFormats = options.remote === true;
        this.usingFallback = models == null && options.fallback !== false;
        if (this.usingFallback) {
          this.reset(FALLBACK_FORMATS, { silent: true });
        }
      },

      /**
       * The constructed URL of the collection (/cn/v2/formats)
       * @returns {string|null} URL used during fetch
       */
      url() {
        return this.formatsServiceUrl;
      },

      /**
       * Retrieve the formats from the Coordinating Node
       * @param {object} [options] Options to pass to Backbone fetch.
       * @augments Backbone.Collection#fetch
       * @returns {jqXHR} Backbone fetch request.
       */
      fetch(options) {
        const fetchOptions = { dataType: "text", ...options };
        const { success, error } = fetchOptions;
        this.parseError = null;
        fetchOptions.success = (collection, response, fetchOptionsArg) => {
          if (collection.parseError) {
            const { parseError } = collection;
            Object.assign(collection, {
              lastFetchError: parseError,
              hasRemoteFormats: false,
              usingFallback: true,
              parseError: null,
            });
            if (typeof error === "function") {
              error.call(
                fetchOptions.context,
                collection,
                parseError,
                fetchOptionsArg,
              );
            }
            return;
          }
          Object.assign(collection, {
            hasRemoteFormats: true,
            usingFallback: false,
            lastFetchError: null,
          });
          if (typeof success === "function") {
            success.call(
              fetchOptions.context,
              collection,
              response,
              fetchOptionsArg,
            );
          }
        };
        fetchOptions.error = (collection, response, fetchOptionsArg) => {
          Object.assign(collection, {
            lastFetchError: new Error(
              `Failed to fetch object formats: ${response?.responseText || response?.status || "Unknown error"}`,
            ),
            hasRemoteFormats: false,
            usingFallback: true,
          });
          if (typeof error === "function") {
            error.call(
              fetchOptions.context,
              collection,
              response,
              fetchOptionsArg,
            );
          }
        };
        return Backbone.Collection.prototype.fetch.call(this, fetchOptions);
      },

      /**
       * Fetch the formats and return a promise that settles with the Backbone
       * sync or error event.
       * @param {object} [options] Options to pass to {@link ObjectFormats#fetch}
       * @returns {Promise<ObjectFormats>} Promise resolving to this collection
       * @since 2.39.0
       */
      fetchPromise(options) {
        if (this.fetchingPromise) return this.fetchingPromise;
        const listener = new Backbone.Model();
        let resolveFetch;
        let rejectFetch;
        const promise = new Promise((resolve, reject) => {
          resolveFetch = resolve;
          rejectFetch = reject;
        });
        const done = () => {
          listener.stopListening();
          this.fetchingPromise = null;
        };

        this.fetchingPromise = promise;
        listener.listenToOnce(this, "sync", () => {
          const { lastFetchError } = this;
          done();
          if (lastFetchError) {
            rejectFetch(lastFetchError);
          } else {
            resolveFetch(this);
          }
        });
        listener.listenToOnce(this, "error", (_collection, response) => {
          done();
          rejectFetch(response);
        });
        try {
          this.fetch(options);
        } catch (error) {
          done();
          rejectFetch(error);
        }

        return promise;
      },

      /**
       * Parse the XML response from the CN
       * @param {string|object} response XML response text, or parsed formats.
       * @returns {object[]} Parsed object formats.
       */
      parse(response) {
        // If the collection is already parsed, just return it
        if (typeof response === "object") return response;

        try {
          const x2js = new X2JS();
          const formats = x2js.xml_str2json(response);
          const objectFormats = formats?.objectFormatList?.objectFormat;
          if (!objectFormats) {
            throw new Error(
              "Object formats response is missing its format list",
            );
          }
          return objectFormats;
        } catch (error) {
          this.parseError = error;
          return this.toJSON();
        }
      },

      /**
       * Convert a DataONE format ID into a human-readable format name.
       * @param {string} formatId Format ID to convert.
       * @returns {string} Friendly format name, or the original value when no
       * match is available.
       * @since 2.39.0
       */
      getFriendlyFormat(formatId) {
        if (!formatId) return formatId;
        return (
          FRIENDLY_FORMAT_NAMES[formatId] ||
          this.models
            .find((format) => format.get("formatId") === formatId)
            ?.get("formatName") ||
          formatId
        );
      },

      /**
       * Given a formatId, filename, and/or mediaType, return the best matching
       * formatId from the collection.
       * @param {object} props Format matching properties.
       * @param {string} [props.formatId] - The formatId to match against the
       * collection
       * @param {string} [props.existingFormatId] Existing formatId to use
       * when filename and mediaType are ambiguous
       * @param {string} [props.filename] - The filename to match against the
       * collection
       * @param {string} [props.mediaType] - The mediaType to match against the
       * collection
       * @returns {string} - The best matching formatId from the collection, or
       * the default formatId if no match is found.
       * @since 2.39.0
       */
      getFormatId({ formatId, existingFormatId, filename, mediaType }) {
        if (formatId) return formatId;

        const file = ValueUtilities.normalizeText(filename);
        const mt = ValueUtilities.normalizeText(mediaType);
        const ext = ValueUtilities.extractFileExtension(file);
        const singleFormatId = (models) =>
          models.length === 1 ? models[0].get("formatId") : null;

        const formatIdMedia = mt
          ? this.models
              .find((format) => format.get("formatId") === mt)
              ?.get("formatId") ||
            singleFormatId(
              this.models.filter((format) => format.get("mediaType") === mt),
            )
          : null;
        const extensionFormats = ext
          ? this.models.filter(
              (format) =>
                ValueUtilities.normalizeText(
                  format.get("extension"),
                )?.toLowerCase() === ext,
            )
          : [];
        const defaultFormatId = PREFERRED_EXTENSION_FORMAT_IDS[ext];
        const formatIdExt = defaultFormatId
          ? this.models
              .find((format) => format.get("formatId") === defaultFormatId)
              ?.get("formatId")
          : singleFormatId(extensionFormats);
        const existingFormat = extensionFormats.find(
          (format) => format.get("formatId") === existingFormatId,
        );

        // A spreadsheet media type can mask a plain CSV file; prefer the
        // extension so ".csv" files are not reported as Excel.
        if (
          formatIdMedia === "application/vnd.ms-excel" &&
          formatIdExt === "text/csv"
        ) {
          return formatIdExt;
        }

        // A generic ZIP media type identifies the container, not its contents.
        if (
          existingFormat &&
          (!formatIdMedia ||
            mt ===
              ValueUtilities.normalizeText(existingFormat.get("mediaType")) ||
            mt === "application/zip")
        ) {
          return existingFormatId;
        }

        return formatIdMedia || formatIdExt || DEFAULT_FORMAT_ID;
      },

      /**
       * Determine the DataONE format type for a format signal.
       * @param {object} props Format properties.
       * @param {string} [props.formatType] Explicit format type.
       * @param {string} [props.formatId] DataONE format ID.
       * @param {string} [props.filename] File name used for extension matching.
       * @param {string} [props.mediaType] Media type used for format matching.
       * @returns {string|null} One of "RESOURCE", "METADATA", "DATA", or null
       * when no match is available.
       * @since 2.39.0
       */
      getFormatType(props) {
        const { formatType } = props;
        if (formatType) {
          const normalizedFormatType =
            ValueUtilities.normalizeText(formatType)?.toLowerCase();
          const matchedFormatType = FORMAT_TYPE_VALUES.find(
            (type) => type.toLowerCase() === normalizedFormatType,
          );
          if (matchedFormatType) return matchedFormatType;
        }
        const formatId = this.getFormatId(props);
        const format = this.models.find(
          (objectFormat) => objectFormat.get("formatId") === formatId,
        );
        return format ? format.get("formatType") : null;
      },

      /**
       * Determine the general display type for an object.
       * @param {object} [props] Object format and provenance properties
       * @returns {string} A value from {@link ObjectFormats#OBJECT_TYPES}
       * @since 2.39.0
       */
      getType(props = {}) {
        const instanceOfClass = props.prov_instanceOfClass;
        if (typeof instanceOfClass === "undefined") {
          if (props.prov_generated || props.prov_used) {
            return OBJECT_TYPES.PROGRAM;
          }
        } else if (
          instanceOfClass?.some((className) =>
            className.includes(PROVONE_PROGRAM_CLASS),
          )
        ) {
          return OBJECT_TYPES.PROGRAM;
        }

        const { formatId, formatType } = props;
        if (OBJECT_TYPE_FORMAT_IDS.COLLECTION.includes(formatId)) {
          return OBJECT_TYPES.COLLECTION;
        }
        if (OBJECT_TYPE_FORMAT_IDS.PORTAL.includes(formatId)) {
          return OBJECT_TYPES.PORTAL;
        }
        if (formatType === FORMAT_TYPES.METADATA) {
          return OBJECT_TYPES.METADATA;
        }
        if (OBJECT_TYPE_FORMAT_IDS.IMAGE.includes(formatId)) {
          return OBJECT_TYPES.IMAGE;
        }
        if (OBJECT_TYPE_FORMAT_IDS.PDF.includes(formatId)) {
          return OBJECT_TYPES.PDF;
        }
        if (OBJECT_TYPE_FORMAT_IDS.ANNOTATION.includes(formatId)) {
          return OBJECT_TYPES.ANNOTATION;
        }
        return OBJECT_TYPES.DATA;
      },

      /**
       * Check whether format properties identify a previewable image.
       * @param {object} props Object format and provenance properties accepted
       * by {@link ObjectFormats#getType}
       * @returns {boolean} True when the object has an image display type
       * @since 2.39.0
       */
      isImage(props) {
        return this.getType(props) === OBJECT_TYPES.IMAGE;
      },

      /**
       * Check whether format properties identify science metadata.
       * @param {object} props Format properties accepted by
       * {@link ObjectFormats#getFormatType}.
       * @returns {boolean} True when the format type is METADATA.
       * @since 2.39.0
       */
      isMetadata(props) {
        return this.getFormatType(props) === FORMAT_TYPES.METADATA;
      },

      /**
       * Check whether format properties identify data.
       * @param {object} props Format properties accepted by
       * {@link ObjectFormats#getFormatType}.
       * @returns {boolean} True when the format type is DATA.
       * @since 2.39.0
       */
      isData(props) {
        return this.getFormatType(props) === FORMAT_TYPES.DATA;
      },

      /**
       * Check whether format properties identify a resource object.
       * @param {object} props Format properties accepted by
       * {@link ObjectFormats#getFormatType}.
       * @returns {boolean} True when the format type is RESOURCE.
       * @since 2.39.0
       */
      isResource(props) {
        return this.getFormatType(props) === FORMAT_TYPES.RESOURCE;
      },

      /**
       * Check whether format properties identify an EML document.
       * @param {object} props Format properties.
       * @param {string} [props.formatId] DataONE format ID.
       * @param {string} [props.filename] File name used for extension matching.
       * @param {string} [props.mediaType] Media type used for format matching.
       * @returns {boolean} True when the resolved format ID is an EML format.
       * @since 2.39.0
       */
      isEML(props) {
        const formatIdToUse = this.getFormatId(props);
        return EML_FORMATS.some((emlFormat) =>
          ValueUtilities.matchWildcard(formatIdToUse, emlFormat),
        );
      },

      /**
       * Check whether format properties identify an ORE resource map.
       * @param {object} props Format properties.
       * @param {string} [props.formatId] DataONE format ID.
       * @param {string} [props.filename] File name used for extension matching.
       * @param {string} [props.mediaType] Media type used for format matching.
       * @returns {boolean} True when the resolved format ID is the ORE format.
       * @since 2.39.0
       */
      isResourceMap(props) {
        return this.getFormatId(props) === FORMAT_IDS.RESOURCE_MAP;
      },
    },
  );

  /**
   * Convert a DataONE format ID into a human-readable name using built-in
   * fallback knowledge.
   * @param {string} formatId Format ID to convert.
   * @returns {string} Friendly format name, or the original value when no match
   * is available.
   * @since 2.39.0
   */
  ObjectFormats.getFriendlyFormat = (formatId) =>
    FRIENDLY_FORMAT_NAMES[formatId] ||
    FALLBACK_FORMAT_NAMES[formatId] ||
    formatId;

  ObjectFormats.FRIENDLY_FORMAT_NAMES = FRIENDLY_FORMAT_NAMES;

  ObjectFormats.FALLBACK_FORMATS = FALLBACK_FORMATS;

  return ObjectFormats;
});