<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[5486] CalendarServer/trunk/twistedcaldav</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.macosforge.org/projects/calendarserver/changeset/5486">5486</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2010-04-16 12:52:34 -0700 (Fri, 16 Apr 2010)</dd>
</dl>

<h3>Log Message</h3>
<pre>Re-work query and filter behavior to match the changes we did for CalDAV.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunktwistedcaldavcaldavxmlpy">CalendarServer/trunk/twistedcaldav/caldavxml.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavcarddavxmlpy">CalendarServer/trunk/twistedcaldav/carddavxml.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdatafiltersfilterpy">CalendarServer/trunk/twistedcaldav/datafilters/filter.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavmethodreport_addressbook_multigetpy">CalendarServer/trunk/twistedcaldav/method/report_addressbook_multiget.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavmethodreport_addressbook_querypy">CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavmethodreport_commonpy">CalendarServer/trunk/twistedcaldav/method/report_common.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavqueryaddressbookquerypy">CalendarServer/trunk/twistedcaldav/query/addressbookquery.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavqueryqueryfilterpy">CalendarServer/trunk/twistedcaldav/query/queryfilter.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavresourcepy">CalendarServer/trunk/twistedcaldav/resource.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServertrunktwistedcaldavdatafiltersaddressdatapy">CalendarServer/trunk/twistedcaldav/datafilters/addressdata.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavqueryaddressbookqueryfilterpy">CalendarServer/trunk/twistedcaldav/query/addressbookqueryfilter.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunktwistedcaldavcaldavxmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/caldavxml.py (5485 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/caldavxml.py        2010-04-16 14:11:11 UTC (rev 5485)
+++ CalendarServer/trunk/twistedcaldav/caldavxml.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -33,9 +33,7 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><del>-from twistedcaldav.dateops import clipPeriod, timeRangesOverlap
</del><span class="cx"> from twistedcaldav.ical import Component as iComponent
</span><del>-from twistedcaldav.ical import Property as iProperty
</del><span class="cx"> from twistedcaldav.ical import parse_date_or_datetime
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavcarddavxmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/carddavxml.py (5485 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/carddavxml.py        2010-04-16 14:11:11 UTC (rev 5485)
+++ CalendarServer/trunk/twistedcaldav/carddavxml.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -25,9 +25,6 @@
</span><span class="cx"> See draft spec: 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-from twistedcaldav.vcard import Property as iProperty
-from twistedcaldav.vcard import Component
-
</del><span class="cx"> from twext.web2.dav import davxml
</span><span class="cx"> 
</span><span class="cx"> ##
</span><span class="lines">@@ -35,7 +32,6 @@
</span><span class="cx"> ##
</span><span class="cx"> 
</span><span class="cx"> carddav_namespace = &quot;urn:ietf:params:xml:ns:carddav&quot;
</span><del>-addressbookserver_namespace = &quot;http://addressbookserver.org/ns/&quot;
</del><span class="cx"> 
</span><span class="cx"> carddav_compliance = (
</span><span class="cx">     &quot;addressbook&quot;,
</span><span class="lines">@@ -59,69 +55,6 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     namespace = carddav_namespace
</span><span class="cx"> 
</span><del>-class CardDAVFilterElement (CardDAVElement):
-    &quot;&quot;&quot;
-    CardDAV filter element.
-    &quot;&quot;&quot;
-    def __init__(self, *children, **attributes):
-
-        super(CardDAVFilterElement, self).__init__(*children, **attributes)
-
-        qualifier = None
-        filters = []
-
-        for child in self.children:
-            qname = child.qname()
-            
-            if qname in (
-                (carddav_namespace, &quot;is-not-defined&quot;),
-            ):
-                if qualifier is not None:
-                    raise ValueError(&quot;Only one of CardDAV:is-not-defined allowed&quot;)
-                qualifier = child
-
-            else:
-                filters.append(child)
-
-        if qualifier and (len(filters) != 0):
-            raise ValueError(&quot;No other tests allowed when CardDAV:is-not-defined is present&quot;)
-        
-        if self.qname() == (carddav_namespace, &quot;prop-filter&quot;):
-            propfilter_test = attributes.get(&quot;test&quot;, &quot;anyof&quot;)
-            if propfilter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
-                raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
-        else:
-            propfilter_test = &quot;anyof&quot;
-
-        self.propfilter_test = propfilter_test
-        self.qualifier   = qualifier
-        self.filters     = filters
-        self.filter_name = attributes[&quot;name&quot;]
-        if isinstance(self.filter_name, unicode):
-            self.filter_name = self.filter_name.encode(&quot;utf-8&quot;)
-        self.defined     = not self.qualifier or (self.qualifier.qname() != (carddav_namespace, &quot;is-not-defined&quot;))
-
-    def match(self, item):
-        &quot;&quot;&quot;
-        Returns True if the given address book item (either a property or parameter value)
-        matches this filter, False otherwise.
-        &quot;&quot;&quot;
-        
-        # Always return True for the is-not-defined case as the result of this will
-        # be negated by the caller
-        if not self.defined: return True
-
-        if self.qualifier and not self.qualifier.match(item): return False
-
-        if len(self.filters) &gt; 0:
-            allof = self.propfilter_test == &quot;allof&quot;
-            for filter in self.filters:
-                if allof != filter._match(item):
-                    return not allof
-            return allof
-        else:
-            return True
-
</del><span class="cx"> class AddressBookHomeSet (CardDAVElement):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     The address book collections URLs for this principal.
</span><span class="lines">@@ -336,67 +269,21 @@
</span><span class="cx">         
</span><span class="cx">         return False
</span><span class="cx"> 
</span><del>-    def elementFromResource(self, resource):
</del><ins>+    def address(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Return a new AddressData element comprised of the possibly filtered
-        address data from the specified resource. If no filter is being applied
-        read the data directly from the resource without parsing it. If a filter
-        is required, parse the vCard data and filter using this AddressData.
-        @param resource: the resource whose address data is to be returned.
-        @return: an L{AddressData} with the (filtered) address data.
</del><ins>+        Returns an address component derived from this element.
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        # Check for filtering or not
-        if self.children:
-            filtered = self.getFromvCard(resource.vCard())
-            return AddressData.fromAddress(filtered)
-        else:
-            return resource.vCardXML()
</del><ins>+        for data in self.children:
+            if not isinstance(data, davxml.PCDATAElement):
+                return None
+            else:
+                # We guaranteed in __init__() that there is only one child...
+                break
</ins><span class="cx"> 
</span><del>-    def elementFromAddress(self, address):
-        &quot;&quot;&quot;
-        Return a new AddressData element comprised of the possibly filtered
-        address.
-        @param address: the address that is to be filtered and returned.
-        @return: an L{AddressData} with the (filtered) address data.
-        &quot;&quot;&quot;
-        
-        # Check for filtering or not
-        filtered = self.getFromvCard(address)
-        return AddressData.fromAddress(filtered)
</del><ins>+        return None
</ins><span class="cx"> 
</span><del>-    def getFromvCard(self, address):
</del><ins>+    def addressData(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Returns an address object containing the data in the given vCard
-        which is specified by this AddressData.
-        &quot;&quot;&quot;
-        if address.name() != &quot;VCARD&quot;:
-            raise ValueError(&quot;Not a vCard: %r&quot; % (address,))
-
-        # Empty element: get all data
-        if not self.children: return address
-
-        # property filtering
-        # copy requested properties
-        vcard = Component(&quot;VCARD&quot;)
-        allProps = True
-        for property in self.children:
-            if isinstance(property, Property):
-                allProps = False
-                for addressProperty in address.properties(property.attributes[&quot;name&quot;]):
-                    vcard.addProperty(addressProperty)
-        
-        # add required properties
-        if allProps:
-            vcard = address
-        else:
-            for requiredProperty in ('N', 'FN', 'VERSION'):
-                if not vcard.hasProperty(requiredProperty):
-                    vcard.addProperty(address.getProperty(requiredProperty))
-
-        return vcard
-
-    def address(self):
-        &quot;&quot;&quot;
</del><span class="cx">         Returns an address component derived from this element.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         for data in self.children:
</span><span class="lines">@@ -406,7 +293,7 @@
</span><span class="cx">                 # We guaranteed in __init__() that there is only one child...
</span><span class="cx">                 break
</span><span class="cx"> 
</span><del>-        return None # TODO: iComponent.fromString(str(data))
</del><ins>+        return str(data)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class AllProperties (CardDAVEmptyElement):
</span><span class="lines">@@ -453,49 +340,8 @@
</span><span class="cx"> 
</span><span class="cx">     allowed_children = { (carddav_namespace, &quot;prop-filter&quot;): (0, None) }
</span><span class="cx">     allowed_attributes = { &quot;test&quot;: False }
</span><del>-
-
-    def __init__(self, *children, **attributes):
-
-        super(Filter, self).__init__(*children, **attributes)
-
-        filter_test = attributes.get(&quot;test&quot;, &quot;anyof&quot;)
-        if filter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
-            raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
</del><span class="cx">         
</span><del>-        self.filter_test = filter_test
-
-    def match(self, vcard):
-        &quot;&quot;&quot;
-        Returns True if the given address property matches this filter, False
-        otherwise. Empty element means always match.
-        &quot;&quot;&quot;

-        if len(self.children) &gt; 0:
-            allof = self.filter_test == &quot;allof&quot;
-            for propfilter in self.children:
-                if allof != propfilter._match(vcard):
-                    return not allof
-            return allof
-        else:
-            return True
-
-    def valid(self):
-        &quot;&quot;&quot;
-        Indicate whether this filter element's structure is valid wrt vCard
-        data object model.
-        
-        @return: True if valid, False otherwise
-        &quot;&quot;&quot;
-        
-        # Test each property
-        for propfilter in self.children:
-            if not propfilter.valid():
-                return False
-        else:
-            return True
-        
-class PropertyFilter (CardDAVFilterElement):
</del><ins>+class PropertyFilter (CardDAVElement):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Limits a search to specific properties.
</span><span class="cx">     (CardDAV-access-09, section 10.5.1)
</span><span class="lines">@@ -512,26 +358,7 @@
</span><span class="cx">         &quot;test&quot;: False,
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    def _match(self, vcard):
-        # At least one property must match (or is-not-defined is set)
-        for property in vcard.properties():
-            if property.name() == self.filter_name and self.match(property): break
-        else:
-            return not self.defined
-        return self.defined
-
-    def valid(self):
-        &quot;&quot;&quot;
-        Indicate whether this filter element's structure is valid wrt vCard
-        data object model.
-        
-        @return:      True if valid, False otherwise
-        &quot;&quot;&quot;
-        
-        # No tests
-        return True
-
-class ParameterFilter (CardDAVFilterElement):
</del><ins>+class ParameterFilter (CardDAVElement):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Limits a search to specific parameters.
</span><span class="cx">     (CardDAV, section 10.5.2)
</span><span class="lines">@@ -544,17 +371,6 @@
</span><span class="cx">     }
</span><span class="cx">     allowed_attributes = { &quot;name&quot;: True }
</span><span class="cx"> 
</span><del>-    def _match(self, property):
-
-        # At least one parameter must match (or is-not-defined is set)
-        result = not self.defined
-        for parameterName in property.params().keys():
-            if parameterName == self.filter_name and self.match(property.params()[parameterName]):
-                result = self.defined
-                break
-
-        return result
-
</del><span class="cx"> class Limit (davxml.WebDAVElement):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Client supplied limit for reports.
</span><span class="lines">@@ -580,13 +396,6 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     name = &quot;is-not-defined&quot;
</span><span class="cx"> 
</span><del>-    def match(self, component):
-        # Oddly, this needs always to return True so that it appears there is
-        # a match - but we then &quot;negate&quot; the result if is-not-defined is set.
-        # Actually this method should never be called as we special case the
-        # is-not-defined option.
-        return True
-
</del><span class="cx"> class TextMatch (CardDAVTextElement):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Specifies a substring match on a property or parameter value.
</span><span class="lines">@@ -610,81 +419,6 @@
</span><span class="cx">         &quot;match-type&quot;: False
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    def __init__(self, *children, **attributes):
-        super(TextMatch, self).__init__(*children, **attributes)
-
-        if &quot;collation&quot; in attributes:
-            self.collation = attributes[&quot;collation&quot;]
-        else:
-            self.collation = &quot;i;unicode-casemap&quot;
-
-        if &quot;negate-condition&quot; in attributes:
-            self.negate = attributes[&quot;negate-condition&quot;]
-            if self.negate not in (&quot;yes&quot;, &quot;no&quot;):
-                self.negate = &quot;no&quot;
-            self.negate = {&quot;yes&quot;: True, &quot;no&quot;: False}[self.negate]
-        else:
-            self.negate = False
-
-        if &quot;match-type&quot; in attributes:
-            self.match_type = attributes[&quot;match-type&quot;]
-            if self.match_type not in (
-                &quot;equals&quot;,
-                &quot;contains&quot;,
-                &quot;starts-with&quot;,
-                &quot;ends-with&quot;,
-            ):
-                self.match_type = &quot;contains&quot;
-        else:
-            self.match_type = &quot;contains&quot;
-
-    def _match(self, item):
-        &quot;&quot;&quot;
-        Match the text for the item.
-        If the item is a property, then match the property value,
-        otherwise it may be a list of parameter values - try to match anyone of those
-        &quot;&quot;&quot;
-        if item is None: return False
-
-        if isinstance(item, iProperty):
-            values = [item.value()]
-        else:
-            values = item
-
-        test = unicode(str(self), &quot;utf-8&quot;).lower()
-
-        def _textCompare(s):
-            s = s.lower()
-            
-            #print(&quot;test=%r, s=%r, matchType=%r&quot; % (test, s, self.match_type))
-            
-            if self.match_type == &quot;equals&quot;:
-                return s == test
-            elif self.match_type == &quot;contains&quot;:
-                return s.find(test) != -1 
-            elif self.match_type == &quot;starts-with&quot;:
-                return s.startswith(test)
-            elif self.match_type == &quot;ends-with&quot;:
-                return s.endswith(test)
-            else:
-                return False
-
-        for value in values:
-            # NB Its possible that we have a text list value which appears as a Python list,
-            # so we need to check for that an iterate over the list.
-            if isinstance(value, list):
-                for subvalue in value:
-                    if _textCompare(unicode(subvalue)):
-                        return not self.negate
-            else:
-                if _textCompare(unicode(value)):
-                    return not self.negate
-        
-        return self.negate
-
-    def match(self, item):
-        return self._match(item)
-
</del><span class="cx"> class AddressBookMultiGet (CardDAVElement):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     CardDAV report used to retrieve specific vCard items via their URIs.
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdatafiltersaddressdatapy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/twistedcaldav/datafilters/addressdata.py (0 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/datafilters/addressdata.py                                (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/addressdata.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -0,0 +1,96 @@
</span><ins>+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.carddavxml import AllProperties
+from twistedcaldav.datafilters.filter import AddressFilter
+from twistedcaldav.vcard import Component
+
+__all__ = [
+    &quot;AddressDataFilter&quot;,
+]
+
+class AddressDataFilter(AddressFilter):
+    &quot;&quot;&quot;
+    Filter using the CARDDAV:address-data element specification
+    &quot;&quot;&quot;
+
+    def __init__(self, addressdata):
+        &quot;&quot;&quot;
+        
+        @param addressdata: the XML element describing how to filter
+        @type addressdata: L{AddressData}
+        &quot;&quot;&quot;
+        
+        self.addressdata = addressdata
+    
+    def filter(self, vcard):
+        &quot;&quot;&quot;
+        Filter the supplied vCard (vobject) data using the request information.
+
+        @param vcard: vCard object
+        @type vcard: L{Component} or C{str}
+        
+        @return: L{Component} for the filtered vcard data
+        &quot;&quot;&quot;
+        
+        # Empty element: get all data
+        if not self.addressdata.children:
+            return vcard
+
+        # Make sure input is valid
+        vcard = self.validAddress(vcard)
+
+        # Filter data based on any provided CARDAV:prop element, or use all current data
+        if self.addressdata.properties:
+            vcard = self.propFilter(self.addressdata.properties, vcard)
+        
+        return vcard
+
+    def propFilter(self, properties, vcard):
+        &quot;&quot;&quot;
+        Returns a vCard component object filtered according to the properties.
+        &quot;&quot;&quot;
+
+        result = Component(&quot;VCARD&quot;)
+
+        xml_properties = properties
+
+        # Empty element means do all properties and components
+        if xml_properties is None:
+            xml_properties = AllProperties()
+
+        if xml_properties is not None:
+            if xml_properties == AllProperties():
+                for vcard_property in vcard.properties():
+                    result.addProperty(vcard_property)
+            else:
+                for xml_property in xml_properties:
+                    name = xml_property.property_name
+                    for vcard_property in vcard.properties(name):
+                        result.addProperty(vcard_property)
+                        
+                # add required properties
+                for requiredProperty in ('N', 'FN', 'VERSION'):
+                    if not result.hasProperty(requiredProperty):
+                        result.addProperty(vcard.getProperty(requiredProperty))
+
+        return result
+
+    def merge(self, vcardnew, vcardold):
+        &quot;&quot;&quot;
+        Address-data merging does not happen
+        &quot;&quot;&quot;
+        raise NotImplementedError
</ins></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdatafiltersfilterpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/datafilters/filter.py (5485 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/datafilters/filter.py        2010-04-16 14:11:11 UTC (rev 5485)
+++ CalendarServer/trunk/twistedcaldav/datafilters/filter.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -1,5 +1,5 @@
</span><span class="cx"> ##
</span><del>-# Copyright (c) 2009 Apple Inc. All rights reserved.
</del><ins>+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
</ins><span class="cx"> #
</span><span class="cx"> # Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
</span><span class="cx"> # you may not use this file except in compliance with the License.
</span><span class="lines">@@ -13,10 +13,13 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><del>-from twistedcaldav.ical import Component
</del><span class="cx"> 
</span><ins>+from twistedcaldav.ical import Component as iComponent
+from twistedcaldav.vcard import Component as vComponent
+
</ins><span class="cx"> __all__ = [
</span><span class="cx">     &quot;CalendarFilter&quot;,
</span><ins>+    &quot;AddressFilter&quot;,
</ins><span class="cx"> ]
</span><span class="cx"> 
</span><span class="cx"> class CalendarFilter(object):
</span><span class="lines">@@ -55,7 +58,7 @@
</span><span class="cx">         # If we were passed a string, parse it out as a Component
</span><span class="cx">         if isinstance(ical, str):
</span><span class="cx">             try:
</span><del>-                ical = Component.fromString(ical)
</del><ins>+                ical = iComponent.fromString(ical)
</ins><span class="cx">             except ValueError:
</span><span class="cx">                 raise ValueError(&quot;Not a calendar: %r&quot; % (ical,))
</span><span class="cx">         
</span><span class="lines">@@ -63,3 +66,48 @@
</span><span class="cx">             raise ValueError(&quot;Not a calendar: %r&quot; % (ical,))
</span><span class="cx">         
</span><span class="cx">         return ical
</span><ins>+
+class AddressFilter(object):
+    &quot;&quot;&quot;
+    Abstract class that defines a vCard filter/merge object
+    &quot;&quot;&quot;
+
+
+    def __init__(self):
+        pass
+    
+    def filter(self, vcard):
+        &quot;&quot;&quot;
+        Filter the supplied vCard (vobject) data using the request information.
+
+        @param vcard: iCalendar object
+        @type vcard: L{Component}
+        
+        @return: L{Component} for the filtered vcard data
+        &quot;&quot;&quot;
+        raise NotImplementedError
+    
+    def merge(self, vcardnew, vcardold):
+        &quot;&quot;&quot;
+        Merge the old vcard (vobject) data into the new vcard data using the request information.
+        
+        @param vcardnew: new vcard object to merge data into
+        @type vcardnew: L{Component}
+        @param vcardold: old vcard data to merge data from
+        @type vcardold: L{Component}
+        &quot;&quot;&quot;
+        raise NotImplementedError
+
+    def validAddress(self, vcard):
+
+        # If we were passed a string, parse it out as a Component
+        if isinstance(vcard, str):
+            try:
+                vcard = vComponent.fromString(vcard)
+            except ValueError:
+                raise ValueError(&quot;Not a vcard: %r&quot; % (vcard,))
+        
+        if vcard is None or vcard.name() != &quot;VCARD&quot;:
+            raise ValueError(&quot;Not a vcard: %r&quot; % (vcard,))
+        
+        return vcard
</ins></span></pre></div>
<a id="CalendarServertrunktwistedcaldavmethodreport_addressbook_multigetpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/method/report_addressbook_multiget.py (5485 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/method/report_addressbook_multiget.py        2010-04-16 14:11:11 UTC (rev 5485)
+++ CalendarServer/trunk/twistedcaldav/method/report_addressbook_multiget.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -1,5 +1,5 @@
</span><span class="cx"> ##
</span><del>-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
</del><ins>+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
</ins><span class="cx"> #
</span><span class="cx"> # Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
</span><span class="cx"> # you may not use this file except in compliance with the License.
</span><span class="lines">@@ -40,8 +40,6 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><del>-max_number_of_addressbook_multigets = 5000
-
</del><span class="cx"> @inlineCallbacks
</span><span class="cx"> def report_urn_ietf_params_xml_ns_carddav_addressbook_multiget(self, request, multiget):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -70,7 +68,7 @@
</span><span class="cx">     request.extendedLogItems[&quot;rcount&quot;] = len(resources)
</span><span class="cx">     
</span><span class="cx">     # Check size of results is within limit
</span><del>-    if len(resources) &gt; max_number_of_addressbook_multigets:
</del><ins>+    if len(resources) &gt; config.MaxAddressBookMultigetHrefs:
</ins><span class="cx">         log.err(&quot;Too many results in multiget report: %d&quot; % len(resources))
</span><span class="cx">         raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits()))
</span><span class="cx"> 
</span><span class="lines">@@ -91,11 +89,6 @@
</span><span class="cx">     else:
</span><span class="cx">         raise AssertionError(&quot;We shouldn't be here&quot;)
</span><span class="cx"> 
</span><del>-    # Check size of results is within limit
-    if len(resources) &gt; config.MaxAddressBookMultigetHrefs:
-        log.err(&quot;Too many results in multiget report: %d&quot; % len(resources))
-        raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (dav_namespace, &quot;number-of-matches-within-limits&quot;)))
-
</del><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Three possibilities exist:
</span><span class="cx">         
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavmethodreport_addressbook_querypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py (5485 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py        2010-04-16 14:11:11 UTC (rev 5485)
+++ CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -1,5 +1,5 @@
</span><span class="cx"> ##
</span><del>-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
</del><ins>+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
</ins><span class="cx"> #
</span><span class="cx"> # Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
</span><span class="cx"> # you may not use this file except in compliance with the License.
</span><span class="lines">@@ -36,6 +36,7 @@
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.carddavxml import carddav_namespace, NResults
</span><span class="cx"> from twistedcaldav.method import report_common
</span><ins>+from twistedcaldav.query import addressbookqueryfilter
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -57,7 +58,8 @@
</span><span class="cx"> 
</span><span class="cx">     responses = []
</span><span class="cx"> 
</span><del>-    filter = addressbook_query.filter
</del><ins>+    xmlfilter = addressbook_query.filter
+    filter = addressbookqueryfilter.Filter(xmlfilter)
</ins><span class="cx">     query  = addressbook_query.query
</span><span class="cx">     limit = addressbook_query.limit
</span><span class="cx"> 
</span><span class="lines">@@ -275,7 +277,6 @@
</span><span class="cx">         yield report_common.applyToAddressBookCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),))
</span><span class="cx">     except NumberOfMatchesWithinLimits, e:
</span><span class="cx">         self.log_info(&quot;Too many matching components in addressbook-query report. Limited to %d items&quot; % e.maxLimit())
</span><del>-        #raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits()))
</del><span class="cx">         responses.append(davxml.StatusResponse(
</span><span class="cx">                         davxml.HRef.fromString(request.uri),
</span><span class="cx">                         davxml.Status.fromResponseCode(responsecode.INSUFFICIENT_STORAGE_SPACE),
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavmethodreport_commonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py (5485 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/method/report_common.py        2010-04-16 14:11:11 UTC (rev 5485)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -56,9 +56,11 @@
</span><span class="cx"> from twistedcaldav import caldavxml
</span><span class="cx"> from twistedcaldav import carddavxml
</span><span class="cx"> from twistedcaldav.caldavxml import caldav_namespace, CalendarData
</span><ins>+from twistedcaldav.carddavxml import AddressData
</ins><span class="cx"> from twistedcaldav.customxml import TwistedCalendarAccessProperty
</span><span class="cx"> from twistedcaldav.datafilters.calendardata import CalendarDataFilter
</span><span class="cx"> from twistedcaldav.datafilters.privateevents import PrivateEventFilter
</span><ins>+from twistedcaldav.datafilters.addressdata import AddressDataFilter
</ins><span class="cx"> from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap
</span><span class="cx"> from twistedcaldav.ical import Component, Property, iCalendarProductID
</span><span class="cx"> from twistedcaldav.instance import InstanceList
</span><span class="lines">@@ -123,7 +125,6 @@
</span><span class="cx">     # First check the privilege on this resource
</span><span class="cx">     if privileges:
</span><span class="cx">         try:
</span><del>-            print (&quot;DeleteResource.applyToAddressBookCollections(1.5)&quot;)
</del><span class="cx">             yield resource.checkPrivileges(request, privileges)
</span><span class="cx">         except AccessDeniedError:
</span><span class="cx">             returnValue( None )
</span><span class="lines">@@ -135,12 +136,13 @@
</span><span class="cx">         resources = [(resource, request_uri)]
</span><span class="cx">     else:
</span><span class="cx">         resources = []
</span><del>-        yield resource.findCalendarCollections(depth, request, lambda x, y: resources.append((x, y)), privileges = privileges)
</del><ins>+        yield resource.findAddressBookCollections(depth, request, lambda x, y: resources.append((x, y)), privileges = privileges)
</ins><span class="cx">          
</span><span class="cx">     for addrresource, uri in resources:
</span><del>-        yield apply(addrresource, uri)
</del><ins>+        result = yield apply(addrresource, uri)
+        if not result:
+            break
</ins><span class="cx"> 
</span><del>-
</del><span class="cx"> def responseForHref(request, responses, href, resource, calendar, timezone, propertiesForResource, propertyreq, isowner=True, vcard=None):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Create an appropriate property status response for the given resource.
</span><span class="lines">@@ -352,12 +354,10 @@
</span><span class="cx">             continue
</span><span class="cx">     
</span><span class="cx">         if isinstance(property, carddavxml.AddressData):
</span><del>-            if vcard:
-                propvalue = property.elementFromAddress(vcard)
-            else:
-                propvalue = property.elementFromResource(resource)
-            if propvalue is None:
-                raise ValueError(&quot;Invalid CardDAV:address-data for request: %r&quot; % (property,))
</del><ins>+            if vcard is None:
+                vcard = (yield resource.vCard())
+            filtered = AddressDataFilter(property).filter(vcard)
+            propvalue = AddressData().fromAddress(filtered)
</ins><span class="cx">             properties_by_status[responsecode.OK].append(propvalue)
</span><span class="cx">             continue
</span><span class="cx">     
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavqueryaddressbookquerypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/query/addressbookquery.py (5485 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/query/addressbookquery.py        2010-04-16 14:11:11 UTC (rev 5485)
+++ CalendarServer/trunk/twistedcaldav/query/addressbookquery.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -1,5 +1,5 @@
</span><span class="cx"> ##
</span><del>-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
</del><ins>+# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
</ins><span class="cx"> #
</span><span class="cx"> # Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
</span><span class="cx"> # you may not use this file except in compliance with the License.
</span><span class="lines">@@ -26,8 +26,7 @@
</span><span class="cx">     &quot;sqladdressbookquery&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-from twistedcaldav.query import sqlgenerator
-from twistedcaldav.query import expression
</del><ins>+from twistedcaldav.query import expression, sqlgenerator
</ins><span class="cx"> from twistedcaldav import carddavxml
</span><span class="cx"> 
</span><span class="cx"> # SQL Index column (field) names
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavqueryaddressbookqueryfilterpy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/twistedcaldav/query/addressbookqueryfilter.py (0 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/query/addressbookqueryfilter.py                                (rev 0)
+++ CalendarServer/trunk/twistedcaldav/query/addressbookqueryfilter.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -0,0 +1,290 @@
</span><ins>+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+&quot;&quot;&quot;
+Object model of CARDAV:filter element used in an addressbook-query.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;Filter&quot;,
+]
+
+from twext.python.log import Logger
+
+from twistedcaldav.carddavxml import carddav_namespace
+from twistedcaldav.vcard import Property
+
+log = Logger()
+
+class FilterBase(object):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+        self.xmlelement = xml_element
+
+    def match(self, item, access=None):
+        raise NotImplementedError
+
+    def valid(self, level=0):
+        raise NotImplementedError
+
+class Filter(FilterBase):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+
+        super(Filter, self).__init__(xml_element)
+
+        filter_test = xml_element.attributes.get(&quot;test&quot;, &quot;anyof&quot;)
+        if filter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
+            raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
+        
+        self.filter_test = filter_test
+
+        self.children = [PropertyFilter(child) for child in xml_element.children]
+
+    def match(self, vcard):
+        &quot;&quot;&quot;
+        Returns True if the given address property matches this filter, False
+        otherwise. Empty element means always match.
+        &quot;&quot;&quot;
+        
+        if len(self.children) &gt; 0:
+            allof = self.filter_test == &quot;allof&quot;
+            for propfilter in self.children:
+                if allof != propfilter._match(vcard):
+                    return not allof
+            return allof
+        else:
+            return True
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt vCard
+        data object model.
+        
+        @return: True if valid, False otherwise
+        &quot;&quot;&quot;
+        
+        # Test each property
+        for propfilter in self.children:
+            if not propfilter.valid():
+                return False
+        else:
+            return True
+
+class FilterChildBase(FilterBase):
+    &quot;&quot;&quot;
+    CardDAV filter element.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+
+        super(FilterChildBase, self).__init__(xml_element)
+
+        qualifier = None
+        filters = []
+
+        for child in xml_element.children:
+            qname = child.qname()
+            
+            if qname in (
+                (carddav_namespace, &quot;is-not-defined&quot;),
+            ):
+                if qualifier is not None:
+                    raise ValueError(&quot;Only one of CardDAV:is-not-defined allowed&quot;)
+                qualifier = IsNotDefined(child)
+
+            elif qname == (carddav_namespace, &quot;text-match&quot;):
+                filters.append(TextMatch(child))
+
+            elif qname == (carddav_namespace, &quot;param-filter&quot;):
+                filters.append(ParameterFilter(child))
+            else:
+                raise ValueError(&quot;Unknown child element: %s&quot; % (qname,))
+
+        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
+            raise ValueError(&quot;No other tests allowed when CardDAV:is-not-defined is present&quot;)
+            
+        if xml_element.qname() == (carddav_namespace, &quot;prop-filter&quot;):
+            propfilter_test = xml_element.attributes.get(&quot;test&quot;, &quot;anyof&quot;)
+            if propfilter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
+                raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
+        else:
+            propfilter_test = &quot;anyof&quot;
+
+        self.propfilter_test = propfilter_test
+        self.qualifier = qualifier
+        self.filters = filters
+        self.filter_name = xml_element.attributes[&quot;name&quot;]
+        if isinstance(self.filter_name, unicode):
+            self.filter_name = self.filter_name.encode(&quot;utf-8&quot;)
+        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
+
+    def match(self, item):
+        &quot;&quot;&quot;
+        Returns True if the given address book item (either a property or parameter value)
+        matches this filter, False otherwise.
+        &quot;&quot;&quot;
+        
+        # Always return True for the is-not-defined case as the result of this will
+        # be negated by the caller
+        if not self.defined: return True
+
+        if self.qualifier and not self.qualifier.match(item): return False
+
+        if len(self.filters) &gt; 0:
+            allof = self.propfilter_test == &quot;allof&quot;
+            for filter in self.filters:
+                if allof != filter._match(item):
+                    return not allof
+            return allof
+        else:
+            return True
+
+class PropertyFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific properties.
+    &quot;&quot;&quot;
+
+    def _match(self, vcard):
+        # At least one property must match (or is-not-defined is set)
+        for property in vcard.properties():
+            if property.name() == self.filter_name and self.match(property): break
+        else:
+            return not self.defined
+        return self.defined
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt vCard
+        data object model.
+        
+        @return:      True if valid, False otherwise
+        &quot;&quot;&quot;
+        
+        # No tests
+        return True
+
+class ParameterFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific parameters.
+    &quot;&quot;&quot;
+
+    def _match(self, property):
+
+        # At least one parameter must match (or is-not-defined is set)
+        result = not self.defined
+        for parameterName in property.params().keys():
+            if parameterName == self.filter_name and self.match(property.params()[parameterName]):
+                result = self.defined
+                break
+
+        return result
+
+class IsNotDefined (FilterBase):
+    &quot;&quot;&quot;
+    Specifies that the named iCalendar item does not exist.
+    &quot;&quot;&quot;
+
+    def match(self, component, access=None):
+        # Oddly, this needs always to return True so that it appears there is
+        # a match - but we then &quot;negate&quot; the result if is-not-defined is set.
+        # Actually this method should never be called as we special case the
+        # is-not-defined option.
+        return True
+
+class TextMatch (FilterBase):
+    &quot;&quot;&quot;
+    Specifies a substring match on a property or parameter value.
+    &quot;&quot;&quot;
+    def __init__(self, xml_element):
+
+        super(TextMatch, self).__init__(xml_element)
+
+        self.text = str(xml_element)
+
+        if &quot;collation&quot; in xml_element.attributes:
+            self.collation = xml_element.attributes[&quot;collation&quot;]
+        else:
+            self.collation = &quot;i;unicode-casemap&quot;
+
+        if &quot;negate-condition&quot; in xml_element.attributes:
+            self.negate = xml_element.attributes[&quot;negate-condition&quot;]
+            if self.negate not in (&quot;yes&quot;, &quot;no&quot;):
+                self.negate = &quot;no&quot;
+            self.negate = {&quot;yes&quot;: True, &quot;no&quot;: False}[self.negate]
+        else:
+            self.negate = False
+
+        if &quot;match-type&quot; in xml_element.attributes:
+            self.match_type = xml_element.attributes[&quot;match-type&quot;]
+            if self.match_type not in (
+                &quot;equals&quot;,
+                &quot;contains&quot;,
+                &quot;starts-with&quot;,
+                &quot;ends-with&quot;,
+            ):
+                self.match_type = &quot;contains&quot;
+        else:
+            self.match_type = &quot;contains&quot;
+
+    def _match(self, item):
+        &quot;&quot;&quot;
+        Match the text for the item.
+        If the item is a property, then match the property value,
+        otherwise it may be a list of parameter values - try to match anyone of those
+        &quot;&quot;&quot;
+        if item is None: return False
+
+        if isinstance(item, Property):
+            values = [item.value()]
+        else:
+            values = item
+
+        test = unicode(self.text, &quot;utf-8&quot;).lower()
+
+        def _textCompare(s):
+            # Currently ignores the collation and does caseless matching
+            s = s.lower()
+            
+            if self.match_type == &quot;equals&quot;:
+                return s == test
+            elif self.match_type == &quot;contains&quot;:
+                return s.find(test) != -1 
+            elif self.match_type == &quot;starts-with&quot;:
+                return s.startswith(test)
+            elif self.match_type == &quot;ends-with&quot;:
+                return s.endswith(test)
+            else:
+                return False
+
+        for value in values:
+            # NB Its possible that we have a text list value which appears as a Python list,
+            # so we need to check for that an iterate over the list.
+            if isinstance(value, list):
+                for subvalue in value:
+                    if _textCompare(unicode(subvalue)):
+                        return not self.negate
+            else:
+                if _textCompare(unicode(value)):
+                    return not self.negate
+        
+        return self.negate
</ins></span></pre></div>
<a id="CalendarServertrunktwistedcaldavqueryqueryfilterpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/query/queryfilter.py (5485 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/query/queryfilter.py        2010-04-16 14:11:11 UTC (rev 5485)
+++ CalendarServer/trunk/twistedcaldav/query/queryfilter.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -1,5 +1,5 @@
</span><span class="cx"> ##
</span><del>-# Copyright (c) 2009 Apple Inc. All rights reserved.
</del><ins>+# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
</ins><span class="cx"> #
</span><span class="cx"> # Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
</span><span class="cx"> # you may not use this file except in compliance with the License.
</span><span class="lines">@@ -493,7 +493,7 @@
</span><span class="cx">             negate = xml_element.attributes[&quot;negate-condition&quot;]
</span><span class="cx">             if negate == &quot;yes&quot;:
</span><span class="cx">                 self.negate = True
</span><del>-            elif caseless == &quot;no&quot;:
</del><ins>+            elif negate == &quot;no&quot;:
</ins><span class="cx">                 self.negate = False
</span><span class="cx">         else:
</span><span class="cx">             self.negate = False
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/resource.py (5485 => 5486)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/resource.py        2010-04-16 14:11:11 UTC (rev 5485)
+++ CalendarServer/trunk/twistedcaldav/resource.py        2010-04-16 19:52:34 UTC (rev 5486)
</span><span class="lines">@@ -674,9 +674,12 @@
</span><span class="cx">         return self.isCalendarCollection()
</span><span class="cx"> 
</span><span class="cx">     def findCalendarCollections(self, depth, request, callback, privileges=None):
</span><del>-        &quot;&quot;&quot;
-        See L{ICalDAVResource.findCalendarCollections}.
-        &quot;&quot;&quot;
</del><ins>+        return self.findSpecialCollections(caldavxml.Calendar, depth, request, callback, privileges)
+
+    def findAddressBookCollections(self, depth, request, callback, privileges=None):
+        return self.findSpecialCollections(carddavxml.AddressBook, depth, request, callback, privileges)
+
+    def findSpecialCollections(self, type, depth, request, callback, privileges=None):
</ins><span class="cx">         assert depth in (&quot;0&quot;, &quot;1&quot;, &quot;infinity&quot;), &quot;Invalid depth: %s&quot; % (depth,)
</span><span class="cx"> 
</span><span class="cx">         def checkPrivilegesError(failure):
</span><span class="lines">@@ -693,11 +696,11 @@
</span><span class="cx">             return ca
</span><span class="cx"> 
</span><span class="cx">         def gotChild(child, childpath):
</span><del>-            if child.isCalendarCollection():
</del><ins>+            if child.isSpecialCollection(type):
</ins><span class="cx">                 callback(child, childpath)
</span><span class="cx">             elif child.isCollection():
</span><span class="cx">                 if depth == &quot;infinity&quot;:
</span><del>-                    fc = child.findCalendarCollections(depth, request, callback, privileges)
</del><ins>+                    fc = child.findSpecialCollections(type, depth, request, callback, privileges)
</ins><span class="cx">                     fc.addCallback(lambda x: reactor.callLater(0, getChild))
</span><span class="cx">                     return fc
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>