From a28df4dd4b2aaa02f5fe8418d5151f53d1c8600e Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Fri, 6 May 2016 17:53:02 +0200 Subject: [PATCH] Reorganize filters Related to #33 and #372. --- radicale/xmlutils.py | 125 ++++++++++++++++++++++++++++++++++++++----- tests/test_base.py | 123 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+), 13 deletions(-) diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index fb75dff..fb06aa5 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -117,6 +117,108 @@ def _href(collection, href): href.lstrip("/")) +def _comp_match(item, filter_, scope="collection"): + """Check whether the ``item`` matches the comp ``filter_``. + + If ``scope`` is ``"collection"``, the filter is applied on the + item's collection. Otherwise, it's applied on the item. + + See rfc4791-9.7.1. + + """ + filter_length = len(filter_) + if scope == "collection": + tag = item.collection.get_meta("tag") + else: + for component in item.components(): + if component.name in ("VTODO", "VEVENT", "VJOURNAL"): + tag = component.name + if filter_length == 0: + # Point #1 of rfc4791-9.7.1 + return filter_.get("name") == tag + else: + if filter_length == 1: + if filter_[0].tag == _tag("C", "is-not-defined"): + # Point #2 of rfc4791-9.7.1 + return filter_.get("name") != tag + if filter_[0].tag == _tag("C", "time-range"): + # Point #3 of rfc4791-9.7.1 + if not _time_range_match(item, filter_[0]): + return False + filter_.remove(filter_[0]) + # Point #4 of rfc4791-9.7.1 + return all( + _prop_match(item, child) if child.tag == _tag("C", "prop-filter") + else _comp_match(item, child, scope="component") + for child in filter_) + + +def _prop_match(item, filter_): + """Check whether the ``item`` matches the prop ``filter_``. + + See rfc4791-9.7.2 and rfc6352-10.5.1. + + """ + filter_length = len(filter_) + if item.collection.get_meta("tag") == "VCALENDAR": + for component in item.components(): + if component.name in ("VTODO", "VEVENT", "VJOURNAL"): + vobject_item = component + else: + vobject_item = item.item + if filter_length == 0: + # Point #1 of rfc4791-9.7.2 + return filter_.get("name").lower() in vobject_item.contents + else: + if filter_length == 1: + if filter_[0].tag == _tag("C", "is-not-defined"): + # Point #2 of rfc4791-9.7.2 + return filter_.get("name").lower() not in vobject_item.contents + if filter_[0].tag == _tag("C", "time-range"): + # Point #3 of rfc4791-9.7.2 + if not _time_range_match(item, filter_[0]): + return False + filter_.remove(filter_[0]) + elif filter_[0].tag == _tag("C", "text-match"): + # Point #4 of rfc4791-9.7.2 + if not _text_match(item, filter_[0]): + return False + filter_.remove(filter_[0]) + return all( + _param_filter_match(item, param_filter) + for param_filter in filter_) + + +def _time_range_match(item, _filter): + """Check whether the ``item`` matches the time-range ``filter_``. + + See rfc4791-9.9. + + """ + # TODO: implement this + return True + + +def _text_match(item, _filter): + """Check whether the ``item`` matches the text-match ``filter_``. + + See rfc4791-9.7.3. + + """ + # TODO: implement this + return True + + +def _param_filter_match(item, _filter): + """Check whether the ``item`` matches the param-filter ``filter_``. + + See rfc4791-9.7.3. + + """ + # TODO: implement this + return True + + def name_from_path(path, collection): """Return Radicale item name from ``path``.""" collection_parts = collection.path.strip("/").split("/") @@ -491,16 +593,11 @@ def report(path, xml_request, collection): hreferences.add(href_path[len(base_prefix) - 1:]) else: hreferences = (path,) - # TODO: handle other filters - # TODO: handle the nested comp-filters correctly - # Read rfc4791-9.7.1 for info - tag_filters = set( - element.get("name").upper() for element - in root.findall(".//%s" % _tag("C", "comp-filter"))) - tag_filters.discard("VCALENDAR") + filters = ( + root.findall(".//%s" % _tag("C", "filter")) + + root.findall(".//%s" % _tag("CR", "filter"))) else: - hreferences = () - tag_filters = set() + hreferences = filters = () # Writing answer multistatus = ET.Element(_tag("D", "multistatus")) @@ -526,10 +623,12 @@ def report(path, xml_request, collection): items = [collection.get(href) for href, etag in collection.list()] for item in items: - if (tag_filters and - item.name not in tag_filters and - not {tag.upper() for tag in item.contents} & tag_filters): - continue + if filters: + match = ( + _comp_match if collection.get_meta("tag") == "VCALENDAR" + else _prop_match) + if not all(match(item, filter_[0]) for filter_ in filters): + continue found_props = [] not_found_props = [] diff --git a/tests/test_base.py b/tests/test_base.py index a02fd53..131ce3a 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -93,6 +93,129 @@ class BaseRequests: status, headers, answer = self.request("GET", "/calendar.ics/") assert "VEVENT" not in answer + def _test_filter(self, filters): + filters_text = "".join( + "%s" % filter_ for filter_ in filters) + self.request( + "PUT", "/calendar.ics/", "BEGIN:VCALENDAR\r\nEND:VCALENDAR") + event = get_file_content("event.ics") + self.request("PUT", "/calendar.ics/event.ics", event) + status, headers, answer = self.request( + "REPORT", "/calendar.ics", + """ + + + + + %s + """ % filters_text) + return answer + + def test_calendar_tag_filter(self): + """Report request with tag-based filter on calendar.""" + assert "href>/calendar.ics/event.ics"""]) + + def test_item_tag_filter(self): + """Report request with tag-based filter on an item.""" + assert "href>/calendar.ics/event.ics + + """]) + assert "href>/calendar.ics/event.ics + + """]) + + def test_item_not_tag_filter(self): + """Report request with tag-based is-not filter on an item.""" + assert "href>/calendar.ics/event.ics + + + + """]) + assert "href>/calendar.ics/event.ics + + + + """]) + + def test_item_prop_filter(self): + """Report request with prop-based filter on an item.""" + assert "href>/calendar.ics/event.ics + + + + """]) + assert "href>/calendar.ics/event.ics + + + + """]) + + def test_item_not_prop_filter(self): + """Report request with prop-based is-not filter on an item.""" + assert "href>/calendar.ics/event.ics + + + + + + """]) + assert "href>/calendar.ics/event.ics + + + + + + """]) + + def test_mutiple_filters(self): + """Report request with multiple filters on an item.""" + assert "href>/calendar.ics/event.ics + + + + + + """, """ + + + + + + + """]) + assert "href>/calendar.ics/event.ics + + + + """, """ + + + + + + + """]) + assert "href>/calendar.ics/event.ics + + + + + + + """]) + class TestMultiFileSystem(BaseRequests, BaseTest): """Base class for filesystem tests."""