Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/webdav/Condition.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/webdav/Condition.py')
-rw-r--r--src/webdav/Condition.py475
1 files changed, 475 insertions, 0 deletions
diff --git a/src/webdav/Condition.py b/src/webdav/Condition.py
new file mode 100644
index 0000000..76acf94
--- /dev/null
+++ b/src/webdav/Condition.py
@@ -0,0 +1,475 @@
+# pylint: disable-msg=R0921,W0704,R0901,W0511,R0201
+# Copyright 2008 German Aerospace Center (DLR)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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 "AS IS" 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.
+
+
+"""
+This module contains classes for creating a search condition according to the DASL draft.
+The classes will output the WHERE part of a search request to a WebDAV server.
+
+Instances of the classes defined in this module form a tree data structure which represents
+a search condition. This tree is made up of AND-nodes, OR-nodes, Operator- and comparison-
+nodes and from property (i.e. variable) and constant leaf nodes.
+"""
+
+
+import types
+from time import strftime
+from calendar import timegm
+from rfc822 import formatdate
+
+from webdav.Constants import NS_DAV, PROP_LAST_MODIFIED, DATE_FORMAT_ISO8601
+
+
+__version__ = "$Revision$"[11:-2]
+
+
+class ConditionTerm(object):
+ """
+ This is the abstact base class for all condition terms.
+ """
+ def __init__(self):
+ pass
+
+ def toXML(self):
+ """
+ Abstact method which return a XML string which can be passed to a WebDAV server
+ for a search condition.
+ """
+ raise NotImplementedError
+
+ # start Tamino workaround for missing like-op:
+ def postFilter(self, resultSet):
+ """
+ Abstact method for temporary workaround for Tamino's absense of the like-operator.
+ This method shall filter the given result set for those resources which match
+ all Contains-trems.
+ """
+ return resultSet
+ # end of workaround
+
+
+class IsCollectionTerm(ConditionTerm):
+ """ Leaf condition. Checks if the matching resources are collections. """
+
+ def __init__(self):
+ """ Constructor. """
+
+ ConditionTerm.__init__(self)
+
+ def toXML(self):
+ """
+ Returns XML encoding.
+ """
+
+ return "<D:is-collection/>"
+
+
+class Literal(ConditionTerm):
+ """
+ A leaf class for condition expressions representing a constant value.
+ """
+ def __init__(self, literal):
+ ConditionTerm.__init__(self)
+ self.literal = literal
+
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:literal>" + self.literal + "</D:literal>"
+
+
+class UnaryTerm(ConditionTerm):
+ """
+ Base class of all nodes with a single child node.
+ """
+ def __init__(self, child):
+ ConditionTerm.__init__(self)
+ self.child = child
+
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return self.child.toXML()
+
+
+class BinaryTerm(ConditionTerm):
+ """
+ Base class of all nodes with two child nodes
+ """
+ def __init__(self, left, right):
+ ConditionTerm.__init__(self)
+ self.left = left
+ self.right = right
+
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return self.left.toXML() + self.right.toXML()
+
+class TupleTerm(ConditionTerm):
+ """
+ Base class of all nodes with multiple single child nodes.
+ """
+ def __init__(self, terms):
+ ConditionTerm.__init__(self)
+ self.terms = terms
+
+ def addTerm(self, term):
+ '''
+ Removes a term.
+
+ @param term: term to add
+ '''
+ self.terms.append(term)
+
+ def removeTerm(self, term):
+ '''
+ Adds a term.
+
+ @param term: term to remove
+ '''
+ try:
+ self.terms.remove(term)
+ except ValueError:
+ pass
+
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ result = ""
+ for term in self.terms:
+ result += term.toXML()
+ return result
+
+
+class AndTerm(TupleTerm):
+ """
+ This class represents and logical AND-condition with multiple sub terms.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:and>" + TupleTerm.toXML(self) + "</D:and>"
+
+ # start Tamino workaround for missing like-op:
+ def postFilter(self, resultSet):
+ '''
+ Filters the given result set. This is a TAMINO WebDav server workaround
+ for the missing 'like' tag.
+
+ @param resultSet: the result set that needs to be filtered.
+ '''
+ for term in self.terms:
+ filtered = term.postFilter(resultSet)
+ resultSet = filtered
+ return resultSet
+ # end of workaround
+
+class OrTerm(TupleTerm):
+ """
+ This class represents and logical OR-condition with multiple sub terms.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:or>" + TupleTerm.toXML(self) + "</D:or>"
+
+ # start Tamino workaround for missing like-op:
+ def postFilter(self, resultSet):
+ '''
+ Filters the given result set. This is a TAMINO WebDav server workaround
+ for the missing 'like' tag.
+
+ @param resultSet: the result set that needs to be filtered.
+ '''
+ raise NotImplementedError
+
+
+class NotTerm(UnaryTerm):
+ """
+ This class represents a negation term for the contained sub term.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ # start Tamino workaround for missing like-op:
+ if isinstance(self.child, ContainsTerm):
+ return ""
+ # end of workaround
+ return "<D:not>" + UnaryTerm.toXML(self) + "</D:not>"
+
+ # start Tamino workaround for missing like-op:
+ def postFilter(self, resultSet):
+ '''
+ Filters the given result set. This is a TAMINO WebDav server workaround
+ for the missing 'like' tag.
+
+ @param resultSet: the result set that needs to be filtered.
+ '''
+ if isinstance(self.child, ContainsTerm):
+ self.child.negate = 1
+ # TODO: pass on filter
+ return self.child.postFilter(resultSet)
+
+
+class ExistsTerm(UnaryTerm):
+ """
+ Nodes of this class must have a single child with tuple type (of len 2) representing a
+ WebDAV property.
+ This leaf term evaluates to true if the (child) property exists.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return '<D:is-defined><D:prop xmlns="%s"><%s' % self.child + ' /></D:prop></D:is-defined>'
+
+class ContentContainsTerm(UnaryTerm):
+ """
+ This class can be used to search for a given phrase in resources' contents.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:contains>" + self.child + "</D:contains>"
+
+
+
+class BinaryRelationTerm(BinaryTerm):
+ """
+ This is the abstact base class for the following relation operands.
+ """
+ def __init__(self, left, right):
+ BinaryTerm.__init__(self, left, right)
+ if isinstance(self.left, types.StringType): # Must be namespace + name pair
+ self.left = ('DAV:', self.left)
+ if not isinstance(self.right, Literal):
+ self.right = Literal(self.right) # Must be Literal instance
+
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ ## TODO: extract name space and create shortcut for left element
+ return '<D:prop xmlns="%s"><%s /></D:prop>' % self.left + self.right.toXML()
+
+
+class StringRelationTerm(BinaryRelationTerm):
+ """
+ This is the abstact base class for the following string relation classes.
+ """
+ def __init__(self, left, right, caseless=None):
+ """
+ @param left: webdav property (namespace, name)
+ @param right: string/unicode literal
+ qparam caseless: 1 for case sensitive comparison
+ """
+ BinaryRelationTerm.__init__(self, left, Literal(right))
+ self.caseless = caseless
+ if self.caseless:
+ self.attrCaseless = "yes"
+ else:
+ self.attrCaseless = "no"
+
+class NumberRelationTerm(BinaryRelationTerm):
+ """
+ This is the abstact base class for the following number comparison classes.
+ """
+ def __init__(self, left, right):
+ """
+ @param left: webdav property (namespace, name)
+ @param right: constant number
+ """
+ ## TODO: implemet typed literal
+ BinaryRelationTerm.__init__(self, left, Literal(str(right)))
+
+class DateRelationTerm(BinaryRelationTerm):
+ """
+ This is the abstact base class for the following date comparison classes.
+ """
+ def __init__(self, left, right):
+ """
+ @param left: webdav property (namespace, name)
+ @param right: string literal containing a date in ISO8601 format
+ """
+ ## TODO: implemet typed literal
+ assert len(right) == 9, "No time is specified for literal: " + str(right)
+ BinaryRelationTerm.__init__(self, left, right)
+ if self.left == (NS_DAV, PROP_LAST_MODIFIED):
+ rfc822Time = formatdate(timegm(right)) # must not use locale setting
+ self.right = Literal(rfc822Time)
+ else:
+ self.right = Literal(strftime(DATE_FORMAT_ISO8601, right))
+
+
+class MatchesTerm(StringRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (child) property's value matches the (child) string.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return '<D:eq caseless="%s">' % self.attrCaseless + StringRelationTerm.toXML(self) + "</D:eq>"
+
+class ContainsTerm(StringRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (left child) property's value contains the
+ (right child) string.
+ """
+ def __init__(self, left, right, isTaminoWorkaround=False):
+ right = unicode(right)
+ StringRelationTerm.__init__(self, left, "%" + right + "%")
+ # Tamino workaround: operator like is not yet implemented:
+ self.negate = 0
+ self.isTaminoWorkaround = isTaminoWorkaround
+
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ # Tamino workaround: operator like is not yet implemented:
+ # Produce a is-defined-condition instead
+ if self.isTaminoWorkaround:
+ return "<D:isdefined><D:prop xmlns='%s'><%s" % self.left + " /></D:prop></D:isdefined>"
+ else:
+ return '<D:like caseless="%s">' % self.attrCaseless + StringRelationTerm.toXML(self) + "</D:like>"
+
+ # start Tamino workaround for missing like-op:
+ def postFilter(self, resultSet):
+ '''
+ Filters the given result set. This is a TAMINO WebDav server workaround
+ for the missing 'like' tag.
+
+ @param resultSet: the result set that needs to be filtered.
+ '''
+ newResult = {}
+ word = self.right.literal[1:-1] # remove leading and trailing '%' characters (see __init__())
+ for url, properties in resultSet.items():
+ value = properties.get(self.left)
+ if self.negate:
+ if not value or value.textof().find(word) < 0:
+ newResult[url] = properties
+ else:
+ if value and value.textof().find(word) >= 0:
+ newResult[url] = properties
+ return newResult
+ # end of workaround
+
+class IsEqualTerm(NumberRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (left child) numerical property's value is equal
+ to the (right child) number.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:eq>" + NumberRelationTerm.toXML(self) + "</D:eq>"
+
+class IsGreaterTerm(NumberRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (left child) numerical property's value is greater
+ than the (right child) number.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:gt>" + NumberRelationTerm.toXML(self) + "</D:gt>"
+
+class IsGreaterOrEqualTerm(NumberRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (left child) numerical property's value is greater
+ than or equal to the (right child) number.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:gte>" + NumberRelationTerm.toXML(self) + "</D:gte>"
+
+class IsSmallerTerm(NumberRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (left child) numerical property's value is less
+ than the (right child) number.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:lt>" + NumberRelationTerm.toXML(self) + "</D:lt>"
+
+class IsSmallerOrEqualTerm(NumberRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (left child) numerical property's value is less
+ than or equal to the (right child) number.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:lte>" + NumberRelationTerm.toXML(self) + "</D:lte>"
+
+
+class OnTerm(DateRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (left child) property's value is a date
+ equal to the (right child) date.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:eq>" + DateRelationTerm.toXML(self) + "</D:eq>"
+
+class AfterTerm(DateRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (left child) property's value is a date
+ succeeding the (right child) date.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:gt>" + DateRelationTerm.toXML(self) + "</D:gt>"
+
+class BeforeTerm(DateRelationTerm):
+ """
+ Nodes of this class evaluate to true if the (left child) property's value is a date
+ preceeding the (right child) date.
+ """
+ def toXML(self):
+ '''
+ Returns XML encoding.
+ '''
+ return "<D:lt>" + DateRelationTerm.toXML(self) + "</D:lt>"
+
+
+
+# Simple module test
+if __name__ == '__main__':
+ # use the example from the webdav specification
+ condition = AndTerm( (MatchesTerm('getcontenttype', 'image/gif'), \
+ IsGreaterTerm('getcontentlength', 4096)) )
+ print "Where: " + condition.toXML()