{"id":32174853,"url":"https://github.com/consbio/gis-metadata-parser","last_synced_at":"2026-02-22T19:03:00.154Z","repository":{"id":57434366,"uuid":"51879278","full_name":"consbio/gis-metadata-parser","owner":"consbio","description":"Parser for GIS metadata standards including ArcGIS, FGDC and ISO-19115","archived":false,"fork":false,"pushed_at":"2021-12-20T18:45:25.000Z","size":408,"stargazers_count":23,"open_issues_count":0,"forks_count":4,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-10-21T19:24:20.491Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/consbio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-02-16T23:47:37.000Z","updated_at":"2025-08-17T03:19:07.000Z","dependencies_parsed_at":"2022-08-27T23:11:02.830Z","dependency_job_id":null,"html_url":"https://github.com/consbio/gis-metadata-parser","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/consbio/gis-metadata-parser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/consbio%2Fgis-metadata-parser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/consbio%2Fgis-metadata-parser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/consbio%2Fgis-metadata-parser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/consbio%2Fgis-metadata-parser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/consbio","download_url":"https://codeload.github.com/consbio/gis-metadata-parser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/consbio%2Fgis-metadata-parser/sbom","scorecard":{"id":302985,"data":{"date":"2025-08-11","repo":{"name":"github.com/consbio/gis-metadata-parser","commit":"a3cac07857bc425185d43ec819aece1a9533ec8c"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-17T21:14:19.268Z","repository_id":57434366,"created_at":"2025-08-17T21:14:19.268Z","updated_at":"2025-08-17T21:14:19.268Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29723573,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T15:10:41.462Z","status":"ssl_error","status_checked_at":"2026-02-22T15:10:04.636Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-21T19:12:18.191Z","updated_at":"2026-02-22T19:03:00.149Z","avatar_url":"https://github.com/consbio.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gis-metadata-parser\n\nXML parsers for GIS metadata that are designed to read in, validate, update and output a core set of properties that have been mapped between the most common standards, currently:\n\n* FGDC\n* ISO-19139 (and ISO-19115)\n* ArcGIS (tested with ArcGIS format 1.0).\n\nThis library is compatible with Python versions 2.7 and 3.4 through 3.6.\n\n[![Build Status](https://api.travis-ci.com/consbio/gis-metadata-parser.png?branch=main)](https://app.travis-ci.com/github/consbio/gis-metadata-parser)\n[![Coverage Status](https://coveralls.io/repos/github/consbio/gis-metadata-parser/badge.svg?branch=main)](https://coveralls.io/github/consbio/gis-metadata-parser?branch=main)\n\n## Installation\nInstall with `pip install gis-metadata-parser`.\n\n## Usage\n\nParsers can be instantiated from files, XML strings or URLs. They can be converted from one standard to another as well.\n```python\nfrom gis_metadata.arcgis_metadata_parser import ArcGISParser\nfrom gis_metadata.fgdc_metadata_parser import FgdcParser\nfrom gis_metadata.iso_metadata_parser import IsoParser\nfrom gis_metadata.metadata_parser import get_metadata_parser\n\n# From file objects\nwith open(r'/path/to/metadata.xml') as metadata:\n    fgdc_from_file = FgdcParser(metadata)\n\nwith open(r'/path/to/metadata.xml') as metadata:\n    iso_from_file = IsoParser(metadata)\n\n# Detect standard based on root element, metadata\nfgdc_from_string = get_metadata_parser(\n    \"\"\"\n    \u003c?xml version='1.0' encoding='UTF-8'?\u003e\n    \u003cmetadata\u003e\n        \u003cidinfo\u003e\n        \u003c/idinfo\u003e\n    \u003c/metadata\u003e\n    \"\"\"\n)\n\n# Detect ArcGIS standard based on root element and its nodes\niso_from_string = get_metadata_parser(\n    \"\"\"\n    \u003c?xml version='1.0' encoding='UTF-8'?\u003e\n    \u003cmetadata\u003e\n        \u003cdataIdInfo/\u003e\u003c/dataIdInfo\u003e\n        \u003cdistInfo/\u003e\u003c/distInfo\u003e\n        \u003cdqInfo/\u003e\u003c/dqInfo\u003e\n    \u003c/metadata\u003e\n    \"\"\"\n)\n\n# Detect ISO standard based on root element, MD_Metadata or MI_Metadata\niso_from_string = get_metadata_parser(\n    \"\"\"\n    \u003c?xml version='1.0' encoding='UTF-8'?\u003e\n    \u003cMD_Metadata\u003e\n        \u003cidentificationInfo\u003e\n        \u003c/identificationInfo\u003e\n    \u003c/MD_Metadata\u003e\n    \"\"\"\n)\n\n# Convert from one standard to another\nfgdc_converted = iso_from_file.convert_to(FgdcParser)\niso_converted = fgdc_from_file.convert_to(IsoParser)\narcgis_converted = iso_converted.convert_to(ArcGISParser)\n\n# Output supported properties as key value pairs (dict)\nfgdc_key_vals = fgdc_from_file.convert_to(dict)\niso_key_vals = iso_from_file.convert_to(dict)\n```\n\nFinally, the properties of the parser can be updated, validated, applied and output:\n```python\nwith open(r'/path/to/metadata.xml') as metadata:\n    fgdc_from_file = FgdcParser(metadata)\n\n# Example simple properties\nfgdc_from_file.title\nfgdc_from_file.abstract\nfgdc_from_file.place_keywords\nfgdc_from_file.thematic_keywords\n\n# :see: gis_metadata.utils.SUPPORTED_PROPS for list of all supported properties\n\n# Complex properties\nfgdc_from_file.attributes\nfgdc_from_file.bounding_box\nfgdc_from_file.contacts\nfgdc_from_file.dates\nfgdc_from_file.digital_forms\nfgdc_from_file.larger_works\nfgdc_from_file.process_steps\nfgdc_from_file.raster_info\n\n# :see: gis_metadata.utils.COMPLEX_DEFINITIONS for structure of all complex properties\n\n# Update properties\nfgdc_from_file.title = 'New Title'\nfgdc_from_file.dates = {'type': 'single' 'values': '1/1/2016'}\n\n# Apply updates\nfgdc_from_file.validate()                                      # Ensure updated properties are valid\nfgdc_from_file.serialize()                                     # Output updated XML as a string\nfgdc_from_file.write()                                         # Output updated XML to existing file\nfgdc_from_file.write(out_file_or_path='/path/to/updated.xml')  # Output updated XML to new file\n```\n\n## Extending and Customizing\n\n### Tips\n\nThere are a few unwritten (until now) rules about the way the metadata parsers are wired to work:\n\n1. Properties are generally defined by XPATH in each `parser._data_map`\n2. Simple parser properties accept only values of `string` and `list`'s of `string`'s\n3. XPATH's configured in the data map support references to element attributes: `'path/to/element/@attr'`\n4. Complex parser properties are defined by custom parser/updater functions instead of by XPATH\n5. Complex parser properties accept values of type `dict` containing simple properties, or a list of said `dict`'s\n6. XPATH keys in the data map with leading underscores are parsed, but not validated or written out\n7. XPATH keys in the data map that \"shadow\" other properties but with a leading underscore serve as secondary values\n8. Secondary values are used in the absence of a primary value if primary location (element or attribute) is missing\n9. Additional underscores indicate further locations to check for missing values, i.e. `title`, `_title`, `__title`\n\nSome examples of existing secondary properties are as follows:\n```python\n# In the ArcGIS parser for distribution contact phone:\n\nARCGIS_TAG_FORMATS = frozendict({\n     ...\n    'dist_phone': 'distInfo/distributor/distorCont/rpCntInfo/cntPhone/voiceNum',\n    '_dist_phone': 'distInfo/distributor/distorCont/rpCntInfo/voiceNum',  # If not in cntPhone\n    ...\n})\n\n# In the FGDC parser for sub-properties in the contacts definition:\n\nFGDC_DEFINITIONS = dict({k: dict(v) for k, v in iteritems(COMPLEX_DEFINITIONS)})\nFGDC_DEFINITIONS[CONTACTS].update({\n    '_name': '{_name}',\n    '_organization': '{_organization}'\n})\n...\nclass FgdcParser(MetadataParser):\n    ...\n    def _init_data_map(self):\n        ...\n        ct_format = FGDC_TAG_FORMATS[CONTACTS]\n        fgdc_data_structures[CONTACTS] = format_xpaths(\n            ...\n            name=ct_format.format(ct_path='cntperp/cntper'),\n            _name=ct_format.format(ct_path='cntorgp/cntper'),  # If not in cntperp\n            organization=ct_format.format(ct_path='cntperp/cntorg'),\n            _organization=ct_format.format(ct_path='cntorgp/cntorg'),  # If not in cntperp\n        )\n\n# Also see the ISO parser for secondary and tertiary sub-properties in the attributes definition:\n\nISO_DEFINITIONS = dict({k: dict(v) for k, v in iteritems(COMPLEX_DEFINITIONS)})\nISO_DEFINITIONS[ATTRIBUTES].update({\n    '_definition_source': '{_definition_src}',\n    '__definition_source': '{__definition_src}',\n    '___definition_source': '{___definition_src}'\n})\n```\n\n\n### Examples\n\nAny of the supported parsers can be extended to include more of a standard's supported data. In this example we'll add two new properties to the `IsoParser`:\n\n* `metadata_language`: a simple string field describing the language of the metadata file itself (not the dataset)\n* `metadata_contacts`: a complex structure with contact info leveraging and enhancing the existing contact structure\n\nThis example will cover:\n\n1. Adding a new simple property\n2. Configuring a secondary location for a property value\n3. Referencing an element attribute in an XPATH\n4. Adding a new complex property\n5. Customizing the complex property to include a new sub-property\n\nAlso, this example is specifically covered by unit tests.\n\n```python\nfrom gis_metadata.iso_metadata_parser import IsoParser\nfrom gis_metadata.utils import COMPLEX_DEFINITIONS, CONTACTS, format_xpaths, ParserProperty\n\n\nclass CustomIsoParser(IsoParser):\n\n    def _init_data_map(self):\n        super(CustomIsoParser, self)._init_data_map()\n\n        # 1. Basic property: text or list (with secondary location referencing `codeListValue` attribute)\n\n        lang_prop = 'metadata_language'\n        self._data_map[lang_prop] = 'language/CharacterString'                    # Parse from here if present\n        self._data_map['_' + lang_prop] = 'language/LanguageCode/@codeListValue'  # Otherwise, try from here\n\n        # 2. Complex structure (reuse of contacts structure plus phone)\n\n        # 2.1 Define some basic variables\n        ct_prop = 'metadata_contacts'\n        ct_xpath = 'contact/CI_ResponsibleParty/{ct_path}'\n        ct_defintion = COMPLEX_DEFINITIONS[CONTACTS]\n        ct_defintion['phone'] = '{phone}'\n\n        # 2.2 Reuse CONTACT structure to specify locations per prop (adapted from parent to add `phone`)\n        self._data_structures[ct_prop] = format_xpaths(\n            ct_defintion,\n            name=ct_xpath.format(ct_path='individualName/CharacterString'),\n            organization=ct_xpath.format(ct_path='organisationName/CharacterString'),\n            position=ct_xpath.format(ct_path='positionName/CharacterString'),\n            phone=ct_xpath.format(\n                ct_path='contactInfo/CI_Contact/phone/CI_Telephone/voice/CharacterString'\n            ),\n            email=ct_xpath.format(\n                ct_path='contactInfo/CI_Contact/address/CI_Address/electronicMailAddress/CharacterString'\n            )\n        )\n\n        # 2.3 Set the contact root to insert new elements at \"contact\" level given the defined path:\n        #   'contact/CI_ResponsibleParty/...'\n        # By default we would get multiple \"CI_ResponsibleParty\" elements under a single \"contact\"\n        # This way we get multiple \"contact\" elements, each with its own single \"CI_ResponsibleParty\"\n        self._data_map['_{prop}_root'.format(prop=ct_prop)] = 'contact'\n\n        # 2.4 Leverage the default methods for parsing complex properties (or write your own parser/updater)\n        self._data_map[ct_prop] = ParserProperty(self._parse_complex_list, self._update_complex_list)\n\n        # 3. And finally, let the parent validation logic know about the two new custom properties\n\n        self._metadata_props.add(lang_prop)\n        self._metadata_props.add(ct_prop)\n\n\nwith open(r'/path/to/metadata.xml') as metadata:\n    iso_from_file = CustomIsoParser(metadata)\n\niso_from_file.metadata_language\niso_from_file.metadata_contacts\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconsbio%2Fgis-metadata-parser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconsbio%2Fgis-metadata-parser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconsbio%2Fgis-metadata-parser/lists"}