def _update_keywords(self, **update_props):
        """ Update operation for ISO type-specific Keywords metadata: Theme or Place """

        tree_to_update = update_props['tree_to_update']
        prop = update_props['prop']
        values = update_props['values']

        keywords = []

        if prop in KEYWORD_PROPS:
            xpath_root = self._data_map['_keywords_root']
            xpath_map = self._data_structures[prop]

            xtype = xpath_map['keyword_type']
            xroot = xpath_map['keyword_root']
            xpath = xpath_map['keyword']
            ktype = KEYWORD_TYPES[prop]

            # Remove descriptiveKeyword nodes according to type
            for element in get_elements(tree_to_update, xpath_root):
                if get_element_text(element, xtype).lower() == ktype.lower():
                    remove_element(tree_to_update, xpath_root)

            element = insert_element(tree_to_update, 0, xpath_root)
            insert_element(element, 0, xtype, ktype)  # Add the type node

            keywords.extend(update_property(element, xroot, xpath, prop, values))

        return keywords
Example #2
0
def update_complex_list(tree_to_update, xpath_root, xpath_map, prop, values):
    """
    Updates and returns the list of updated complex Elements parsed from tree_to_update.
    :param tree_to_update: the XML tree compatible with element_utils to be updated
    :param xpath_root: the XPATH location of each complex Element
    :param xpath_map: a Dictionary of XPATHs corresponding to the complex structure definition
    :param prop: the property identifying the complex structure to be serialized
    :param values: a List containing the updated complex structures as Dictionaries
    """

    complex_list = []

    remove_element(tree_to_update, xpath_root, True)

    if not values:
        # Returns the elements corresponding to property removed from the tree
        complex_list.append(update_property(tree_to_update, xpath_root, xpath_root, prop, values))
    else:
        for idx, complex_struct in enumerate(wrap_value(values)):

            # Insert a new complex element root for each dict in the list
            complex_element = insert_element(tree_to_update, idx, xpath_root)

            for subprop, value in iteritems(complex_struct):
                xpath = get_xpath_branch(xpath_root, xpath_map[subprop])
                value = get_default_for_complex_sub(prop, subprop, value, xpath)
                complex_list.append(update_property(complex_element, None, xpath, subprop, value))

    return complex_list
    def _update_keywords(self, **update_props):
        """ Update operation for ISO type-specific Keywords metadata: Theme or Place """

        tree_to_update = update_props['tree_to_update']
        prop = update_props['prop']
        values = update_props['values']

        keywords = []

        if prop in [KEYWORDS_PLACE, KEYWORDS_THEME]:
            xpath_root = self._data_map['_keywords_root']
            xpath_map = self._data_structures[update_props['prop']]

            xtype = xpath_map['keyword_type']
            xroot = xpath_map['keyword_root']
            xpath = xpath_map['keyword']

            if prop == KEYWORDS_PLACE:
                ktype = KEYWORD_TYPE_PLACE
            elif prop == KEYWORDS_THEME:
                ktype = KEYWORD_TYPE_THEME

            # Remove descriptiveKeyword nodes according to type
            for element in get_elements(tree_to_update, xpath_root):
                if get_element_text(element, xtype).lower() == ktype.lower():
                    remove_element(tree_to_update, xpath_root)

            element = insert_element(tree_to_update, 0, xpath_root)
            insert_element(element, 0, xtype, ktype)  # Add the type node

            keywords.extend(update_property(element, xroot, xpath, prop, values))

        return keywords
Example #4
0
    def _update_dates(self, xpath_root=None, **update_props):
        """
        Default update operation for Dates metadata
        :see: gis_metadata.utils._complex_definitions[DATES]
        """

        tree_to_update = update_props['tree_to_update']
        prop = update_props['prop']
        values = (update_props['values'] or {}).get(DATE_VALUES) or u''
        xpaths = self._data_structures[prop]

        if not self.dates:
            date_xpaths = xpath_root
        elif self.dates[DATE_TYPE] != DATE_TYPE_RANGE:
            date_xpaths = xpaths.get(self.dates[DATE_TYPE], u'')
        else:
            date_xpaths = [
                xpaths[DATE_TYPE_RANGE_BEGIN], xpaths[DATE_TYPE_RANGE_END]
            ]

        if xpath_root:
            remove_element(tree_to_update, xpath_root)

        return update_property(tree_to_update, xpath_root, date_xpaths, prop,
                               values)
Example #5
0
def update_complex(tree_to_update, xpath_root, xpath_map, prop, values):
    """
    Updates and returns the updated complex Element parsed from tree_to_update.
    :param tree_to_update: the XML tree compatible with element_utils to be updated
    :param xpath_root: the XPATH location of the root of the complex Element
    :param xpath_map: a Dictionary of XPATHs corresponding to the complex structure definition
    :param prop: the property identifying the complex structure to be serialized
    :param values: a Dictionary representing the complex structure to be updated
    """

    remove_element(tree_to_update, xpath_root, True)

    values = reduce_value(values, {})

    if not values:
        # Returns the elements corresponding to property removed from the tree
        updated = update_property(tree_to_update, xpath_root, xpath_root, prop, values)
    else:
        for subprop, value in iteritems(values):
            xpath = xpath_map[subprop]
            value = get_default_for_complex_sub(prop, subprop, value, xpath)
            update_property(tree_to_update, None, xpath, subprop, value)
        updated = get_element(tree_to_update, xpath_root)

    return updated
Example #6
0
    def update_element(elem, idx, root, path, vals):
        """ Internal helper function to encapsulate single item update """

        has_root = bool(root and len(path) > len(root) and path.startswith(root))
        path, attr = get_xpath_tuple(path)  # 'path/@attr' to ('path', 'attr')

        if attr:
            removed = [get_element(elem, path)]
            remove_element_attributes(removed[0], attr)
        elif not has_root:
            removed = wrap_value(remove_element(elem, path))
        else:
            path = get_xpath_branch(root, path)
            removed = [] if idx != 0 else [remove_element(e, path, True) for e in get_elements(elem, root)]

        if not vals:
            return removed

        items = []

        for i, val in enumerate(wrap_value(vals)):
            elem_to_update = elem

            if has_root:
                elem_to_update = insert_element(elem, (i + idx), root)

            val = val.decode('utf-8') if not isinstance(val, string_types) else val
            if not attr:
                items.append(insert_element(elem_to_update, i, path, val))
            else:
                items.append(insert_element(elem_to_update, i, path, **{attr: val}))

        return items
Example #7
0
def update_complex_list(tree_to_update, xpath_root, xpath_map, prop, values):
    """
    Updates and returns the list of updated complex Elements parsed from tree_to_update.
    :param tree_to_update: the XML tree compatible with element_utils to be updated
    :param xpath_root: the XPATH location of each complex Element
    :param xpath_map: a Dictionary of XPATHs corresponding to the complex structure definition
    :param prop: the property identifying the complex structure to be serialized
    :param values: a List containing the updated complex structures as Dictionaries
    """

    complex_list = []

    remove_element(tree_to_update, xpath_root, True)

    if not values:
        # Returns the elements corresponding to property removed from the tree
        complex_list.append(
            update_property(tree_to_update, xpath_root, xpath_root, prop,
                            values))
    else:
        for idx, complex_struct in enumerate(wrap_value(values)):

            # Insert a new complex element root for each dict in the list
            complex_element = insert_element(tree_to_update, idx, xpath_root)

            for subprop, value in iteritems(complex_struct):
                xpath = get_xpath_branch(xpath_root, xpath_map[subprop])
                value = get_default_for_complex_sub(prop, subprop, value,
                                                    xpath)
                complex_list.append(
                    update_property(complex_element, None, xpath, subprop,
                                    value))

    return complex_list
Example #8
0
def update_complex(tree_to_update, xpath_root, xpath_map, prop, values):
    """
    Updates and returns the updated complex Element parsed from tree_to_update.
    :param tree_to_update: the XML tree compatible with element_utils to be updated
    :param xpath_root: the XPATH location of the root of the complex Element
    :param xpath_map: a Dictionary of XPATHs corresponding to the complex structure definition
    :param prop: the property identifying the complex structure to be serialized
    :param values: a Dictionary representing the complex structure to be updated
    """

    remove_element(tree_to_update, xpath_root, True)

    values = reduce_value(values, {})

    if not values:
        # Returns the elements corresponding to property removed from the tree
        updated = update_property(tree_to_update, xpath_root, xpath_root, prop,
                                  values)
    else:
        for subprop, value in iteritems(values):
            xpath = xpath_map[subprop]
            value = get_default_for_complex_sub(prop, subprop, value, xpath)
            update_property(tree_to_update, None, xpath, subprop, value)
        updated = get_element(tree_to_update, xpath_root)

    return updated
    def _update_attribute_details(self, **update_props):
        """ Update operation for ISO Attribute Details metadata: write to "MD_Metadata/featureType" """

        tree_to_update = update_props['tree_to_update']
        xpath = self._data_map['_attr_citation']

        # Cannot write to remote file: remove the featureCatalogueCitation element

        self._attr_details_file_url = None
        remove_element(tree_to_update, xpath, True)

        return self._update_complex_list(**update_props)
    def _update_attribute_details(self, **update_props):
        """ Update operation for ISO Attribute Details metadata: write to "MD_Metadata/featureType" """

        tree_to_update = update_props['tree_to_update']
        xpath = self._data_map['_attr_citation']

        # Cannot write to remote file: remove the featureCatalogueCitation element

        self._attr_details_file_url = None
        remove_element(tree_to_update, xpath, True)

        return self._update_complex_list(**update_props)
Example #11
0
    def update_element(elem, idx, root, path, vals):
        """ Internal helper function to encapsulate single item update """

        has_root = bool(root and len(path) > len(root)
                        and path.startswith(root))
        path, attr = get_xpath_tuple(path)  # 'path/@attr' to ('path', 'attr')

        if attr:
            removed = [get_element(elem, path)]
            remove_element_attributes(removed[0], attr)
        elif not has_root:
            removed = wrap_value(remove_element(elem, path))
        else:
            path = get_xpath_branch(root, path)
            removed = [] if idx != 0 else [
                remove_element(e, path, True)
                for e in get_elements(elem, root)
            ]

        if not vals:
            return removed

        items = []

        for i, val in enumerate(wrap_value(vals)):
            elem_to_update = elem

            if has_root:
                elem_to_update = insert_element(elem, (i + idx), root)

            val = val.decode('utf-8') if not isinstance(val,
                                                        string_types) else val
            if not attr:
                items.append(insert_element(elem_to_update, i, path, val))
            elif path:
                items.append(
                    insert_element(elem_to_update, i, path, **{attr: val}))
            else:
                set_element_attributes(elem_to_update, **{attr: val})
                items.append(elem_to_update)

        return items
    def _update_dates(self, **update_props):
        """
        Update operation for ISO Dates metadata
        :see: gis_metadata.utils._complex_definitions[DATES]
        """

        tree_to_update = update_props['tree_to_update']
        xpath_root = self._data_map['_dates_root']

        if self.dates:
            date_type = self.dates[DATE_TYPE]

            # First remove all date info from common root
            remove_element(tree_to_update, xpath_root)

            if date_type == DATE_TYPE_MULTIPLE:
                xpath_root += '/TimeInstant'
            elif date_type == DATE_TYPE_RANGE:
                xpath_root += '/TimePeriod'

        return super(IsoParser, self)._update_dates(xpath_root, **update_props)
    def _update_dates(self, **update_props):
        """
        Update operation for ISO Dates metadata
        :see: gis_metadata.utils._complex_definitions[DATES]
        """

        tree_to_update = update_props['tree_to_update']
        xpath_root = self._data_map['_dates_root']

        if self.dates:
            date_type = self.dates[DATE_TYPE]

            # First remove all date info from common root
            remove_element(tree_to_update, xpath_root)

            if date_type == DATE_TYPE_MULTIPLE:
                xpath_root += '/TimeInstant'
            elif date_type == DATE_TYPE_RANGE:
                xpath_root += '/TimePeriod'

        return super(IsoParser, self)._update_dates(xpath_root, **update_props)
Example #14
0
    def test_fgdc_parser(self):
        """ Tests behavior unique to the FGDC parser """

        # Test dates structure defaults

        # Remove multiple dates to ensure range is queried
        fgdc_element = get_remote_element(self.fgdc_file)
        remove_element(fgdc_element, "idinfo/timeperd/timeinfo/mdattim", True)

        # Assert that the backup dates are read in successfully
        fgdc_parser = FgdcParser(element_to_string(fgdc_element))
        self.assertEqual(fgdc_parser.dates, {"type": "range", "values": ["Date Range Start", "Date Range End"]})

        # Test contact data structure defaults

        contacts_def = get_complex_definitions()[CONTACTS]

        # Remove the contact organization completely
        fgdc_element = get_remote_element(self.fgdc_file)
        for contact_element in get_elements(fgdc_element, "idinfo/ptcontac"):
            if element_exists(contact_element, "cntinfo/cntorgp"):
                clear_element(contact_element)

        # Assert that the contact organization has been read in
        fgdc_parser = FgdcParser(element_to_string(fgdc_element))
        for key in contacts_def:
            for contact in fgdc_parser.contacts:
                self.assertIsNotNone(contact[key], "Failed to read contact.{0}".format(key))

        # Remove the contact person completely
        fgdc_element = get_remote_element(self.fgdc_file)
        for contact_element in get_elements(fgdc_element, "idinfo/ptcontac"):
            if element_exists(contact_element, "cntinfo/cntperp"):
                clear_element(contact_element)

        # Assert that the contact organization has been read in
        fgdc_parser = FgdcParser(element_to_string(fgdc_element))
        for key in contacts_def:
            for contact in fgdc_parser.contacts:
                self.assertIsNotNone(contact[key], "Failed to read updated contact.{0}".format(key))
Example #15
0
    def test_arcgis_parser(self):
        """ Tests behavior unique to the FGDC parser """

        # Test dates structure defaults

        # Remove multiple dates to ensure range is queried
        arcgis_element = get_remote_element(self.arcgis_file)
        remove_element(arcgis_element, "dataIdInfo/dataExt/tempEle/TempExtent/exTemp/TM_Instant", True)

        # Assert that the backup dates are read in successfully
        arcgis_parser = ArcGISParser(element_to_string(arcgis_element))
        self.assertEqual(arcgis_parser.dates, {"type": "range", "values": ["Date Range Start", "Date Range End"]})

        # Remove one of the date range values and assert that only the end date is read in as a single
        remove_element(arcgis_element, "dataIdInfo/dataExt/tempEle/TempExtent/exTemp/TM_Period/tmBegin", True)
        arcgis_parser = ArcGISParser(element_to_string(arcgis_element))
        self.assertEqual(arcgis_parser.dates, {"type": "single", "values": ["Date Range End"]})

        # Remove the last of the date range values and assert that no dates are read in
        remove_element(arcgis_element, "dataIdInfo/dataExt/tempEle/TempExtent/exTemp/TM_Period", True)
        arcgis_parser = ArcGISParser(element_to_string(arcgis_element))
        self.assertEqual(arcgis_parser.dates, {})

        # Insert a single date value and assert that only it is read in

        single_path = "dataIdInfo/dataExt/tempEle/TempExtent/exTemp/TM_Instant/tmPosition"
        single_text = "Single Date"
        insert_element(arcgis_element, 0, single_path, single_text)

        arcgis_parser = ArcGISParser(element_to_string(arcgis_element))
        self.assertEqual(arcgis_parser.dates, {"type": "single", "values": [single_text]})
Example #16
0
    def test_parser_values(self):
        """ Tests that parsers are populated with the expected values """

        arcgis_element = get_remote_element(self.arcgis_file)
        arcgis_parser = ArcGISParser(element_to_string(arcgis_element))
        arcgis_new = ArcGISParser(**TEST_METADATA_VALUES)

        # Test that the two ArcGIS parsers have the same data given the same input file
        self.assert_parsers_are_equal(arcgis_parser, arcgis_new)

        fgdc_element = get_remote_element(self.fgdc_file)
        fgdc_parser = FgdcParser(element_to_string(fgdc_element))
        fgdc_new = FgdcParser(**TEST_METADATA_VALUES)

        # Test that the two FGDC parsers have the same data given the same input file
        self.assert_parsers_are_equal(fgdc_parser, fgdc_new)

        iso_element = get_remote_element(self.iso_file)
        remove_element(iso_element, _iso_tag_formats["_attr_citation"], True)
        iso_parser = IsoParser(element_to_string(iso_element))
        iso_new = IsoParser(**TEST_METADATA_VALUES)

        # Test that the two ISO parsers have the same data given the same input file
        self.assert_parsers_are_equal(iso_parser, iso_new)

        # Test that all distinct parsers have the same data given equivalent input files

        self.assert_parsers_are_equal(arcgis_parser, fgdc_parser)
        self.assert_parsers_are_equal(fgdc_parser, iso_parser)
        self.assert_parsers_are_equal(iso_parser, arcgis_parser)

        # Test that each parser's values correspond to the target values
        for parser in (arcgis_parser, fgdc_parser, iso_parser):
            parser_type = type(parser)

            for prop, target in TEST_METADATA_VALUES.items():
                self.assert_equal_for(parser_type, prop, getattr(parser, prop), target)