<!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>[15377] PyCalendar/branches/patch/src/pycalendar</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.calendarserver.org//changeset/15377">15377</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-12-10 14:01:17 -0800 (Thu, 10 Dec 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Initial support for icalendar-patch.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#PyCalendarbranchespatchsrcpycalendarcomponentbasepy">PyCalendar/branches/patch/src/pycalendar/componentbase.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendardurationpy">PyCalendar/branches/patch/src/pycalendar/duration.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarfloatvaluepy">PyCalendar/branches/patch/src/pycalendar/floatvalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendargeovaluepy">PyCalendar/branches/patch/src/pycalendar/geovalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendaricalendarcalendarpy">PyCalendar/branches/patch/src/pycalendar/icalendar/calendar.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendaricalendarcomponentrecurpy">PyCalendar/branches/patch/src/pycalendar/icalendar/componentrecur.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendaricalendarrecurrencepy">PyCalendar/branches/patch/src/pycalendar/icalendar/recurrence.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendaricalendarrequeststatusvaluepy">PyCalendar/branches/patch/src/pycalendar/icalendar/requeststatusvalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendaricalendartestsrrule_examplesjson">PyCalendar/branches/patch/src/pycalendar/icalendar/tests/rrule_examples.json</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendaricalendarteststest_recurrencepy">PyCalendar/branches/patch/src/pycalendar/icalendar/tests/test_recurrence.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarintegervaluepy">PyCalendar/branches/patch/src/pycalendar/integervalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarmultivaluepy">PyCalendar/branches/patch/src/pycalendar/multivalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarparserpy">PyCalendar/branches/patch/src/pycalendar/parser.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarperiodpy">PyCalendar/branches/patch/src/pycalendar/period.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarplaintextvaluepy">PyCalendar/branches/patch/src/pycalendar/plaintextvalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendartextvaluepy">PyCalendar/branches/patch/src/pycalendar/textvalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarurivaluepy">PyCalendar/branches/patch/src/pycalendar/urivalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarutcoffsetvaluepy">PyCalendar/branches/patch/src/pycalendar/utcoffsetvalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarutilspy">PyCalendar/branches/patch/src/pycalendar/utils.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarvaluepy">PyCalendar/branches/patch/src/pycalendar/value.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarvalueutilspy">PyCalendar/branches/patch/src/pycalendar/valueutils.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarvcardadrpy">PyCalendar/branches/patch/src/pycalendar/vcard/adr.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarvcardnpy">PyCalendar/branches/patch/src/pycalendar/vcard/n.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarvcardorgvaluepy">PyCalendar/branches/patch/src/pycalendar/vcard/orgvalue.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarvcardteststest_adrpy">PyCalendar/branches/patch/src/pycalendar/vcard/tests/test_adr.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendarvcardteststest_npy">PyCalendar/branches/patch/src/pycalendar/vcard/tests/test_n.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#PyCalendarbranchespatchsrcpycalendaricalendarpatchpy">PyCalendar/branches/patch/src/pycalendar/icalendar/patch.py</a></li>
<li><a href="#PyCalendarbranchespatchsrcpycalendaricalendarteststest_patchpy">PyCalendar/branches/patch/src/pycalendar/icalendar/tests/test_patch.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="PyCalendarbranchespatchsrcpycalendarcomponentbasepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/componentbase.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/componentbase.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/componentbase.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -153,6 +153,14 @@
</span><span class="cx">         self.mComponents.remove(component)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def removeFromParent(self):
+        &quot;&quot;&quot;
+        Remove this L{ComponentBase} from its parent
+        &quot;&quot;&quot;
+        if self.mParentComponent is not None:
+            self.mParentComponent.removeComponent(self)
+
+
</ins><span class="cx">     def removeAllComponent(self, compname=None):
</span><span class="cx">         if compname:
</span><span class="cx">             compname = compname.upper()
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendardurationpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/duration.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/duration.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/duration.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -241,33 +241,40 @@
</span><span class="cx"> 
</span><span class="cx">     def generate(self, os):
</span><span class="cx">         try:
</span><del>-            if not self.mForward and (self.mWeeks or self.mDays or self.mHours or self.mMinutes or self.mSeconds):
-                os.write(&quot;-&quot;)
-            os.write(&quot;P&quot;)
</del><ins>+            os.write(self.getText())
+        except:
+            pass
</ins><span class="cx"> 
</span><del>-            if self.mWeeks != 0:
-                os.write(&quot;%dW&quot; % (self.mWeeks,))
-            else:
-                if self.mDays != 0:
-                    os.write(&quot;%dD&quot; % (self.mDays,))
</del><span class="cx"> 
</span><del>-                if (self.mHours != 0) or (self.mMinutes != 0) or (self.mSeconds != 0):
-                    os.write(&quot;T&quot;)
</del><ins>+    def getText(self):
+        result = []
+        if not self.mForward and (self.mWeeks or self.mDays or self.mHours or self.mMinutes or self.mSeconds):
+            result.append(&quot;-&quot;)
+        result.append(&quot;P&quot;)
</ins><span class="cx"> 
</span><del>-                    if self.mHours != 0:
-                        os.write(&quot;%dH&quot; % (self.mHours,))
</del><ins>+        if self.mWeeks != 0:
+            result.append(&quot;%dW&quot; % (self.mWeeks,))
+        else:
+            if self.mDays != 0:
+                result.append(&quot;%dD&quot; % (self.mDays,))
</ins><span class="cx"> 
</span><del>-                    if (self.mMinutes != 0) or ((self.mHours != 0) and (self.mSeconds != 0)):
-                        os.write(&quot;%dM&quot; % (self.mMinutes,))
</del><ins>+            if (self.mHours != 0) or (self.mMinutes != 0) or (self.mSeconds != 0):
+                result.append(&quot;T&quot;)
</ins><span class="cx"> 
</span><del>-                    if self.mSeconds != 0:
-                        os.write(&quot;%dS&quot; % (self.mSeconds,))
-                elif self.mDays == 0:
-                    os.write(&quot;T0S&quot;)
-        except:
-            pass
</del><ins>+                if self.mHours != 0:
+                    result.append(&quot;%dH&quot; % (self.mHours,))
</ins><span class="cx"> 
</span><ins>+                if (self.mMinutes != 0) or ((self.mHours != 0) and (self.mSeconds != 0)):
+                    result.append(&quot;%dM&quot; % (self.mMinutes,))
</ins><span class="cx"> 
</span><ins>+                if self.mSeconds != 0:
+                    result.append(&quot;%dS&quot; % (self.mSeconds,))
+            elif self.mDays == 0:
+                result.append(&quot;T0S&quot;)
+
+        return &quot;&quot;.join(result)
+
+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx">         node.text = self.getText()
</span><span class="cx"> 
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarfloatvaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/floatvalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/floatvalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/floatvalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -45,6 +45,10 @@
</span><span class="cx">             pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getTextValue(self):
+        return str(self.mValue)
+
+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx">         value = self.getXMLNode(node, namespace)
</span><span class="cx">         value.text = str(self.mValue)
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendargeovaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/geovalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/geovalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/geovalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -63,9 +63,13 @@
</span><span class="cx"> 
</span><span class="cx">     # os - StringIO object
</span><span class="cx">     def generate(self, os):
</span><del>-        os.write(&quot;%s;%s&quot; % (self.mValue[0], self.mValue[1],))
</del><ins>+        os.write(self.getTextValue())
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getTextValue(self):
+        return &quot;%s;%s&quot; % (self.mValue[0], self.mValue[1],)
+
+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx">         value = self.getXMLNode(node, namespace)
</span><span class="cx"> 
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendaricalendarcalendarpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/icalendar/calendar.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/icalendar/calendar.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/icalendar/calendar.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -365,49 +365,10 @@
</span><span class="cx">         master = self.masterComponent()
</span><span class="cx">         if master is None:
</span><span class="cx">             return None
</span><del>-
-        # Create the derived instance
-        newcomp = master.duplicate()
-
-        # Strip out unwanted recurrence properties
-        for propname in (
-            definitions.cICalProperty_RRULE,
-            definitions.cICalProperty_RDATE,
-            definitions.cICalProperty_EXRULE,
-            definitions.cICalProperty_EXDATE,
-            definitions.cICalProperty_RECURRENCE_ID,
-        ):
-            newcomp.removeProperties(propname)
-
-        # New DTSTART is the RECURRENCE-ID we are deriving but adjusted to the
-        # original DTSTART's localtime
-        dtstart = newcomp.getStart()
-        dtend = newcomp.getEnd()
-        oldduration = dtend - dtstart
-
-        newdtstartValue = recurrenceID.duplicate()
-        if not dtstart.isDateOnly():
-            if dtstart.local():
-                newdtstartValue.adjustTimezone(dtstart.getTimezone())
</del><span class="cx">         else:
</span><del>-            newdtstartValue.setDateOnly(True)
</del><ins>+            return master.deriveComponent(recurrenceID)
</ins><span class="cx"> 
</span><del>-        newcomp.removeProperties(definitions.cICalProperty_DTSTART)
-        newcomp.removeProperties(definitions.cICalProperty_DTEND)
-        prop = Property(definitions.cICalProperty_DTSTART, newdtstartValue)
-        newcomp.addProperty(prop)
-        if not newcomp.useDuration():
-            prop = Property(definitions.cICalProperty_DTEND, newdtstartValue + oldduration)
-            newcomp.addProperty(prop)
</del><span class="cx"> 
</span><del>-        newcomp.addProperty(Property(&quot;RECURRENCE-ID&quot;, newdtstartValue))
-
-        # After creating/changing a component we need to do this to keep PyCalendar happy
-        newcomp.finalise()
-
-        return newcomp
-
-
</del><span class="cx">     def masterComponent(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Return the first sub-component of a recurring type that represents the master
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendaricalendarcomponentrecurpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/icalendar/componentrecur.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/icalendar/componentrecur.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/icalendar/componentrecur.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -719,5 +719,60 @@
</span><span class="cx">                 self.mEnd = self.mStart + temp
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def deriveComponent(self, recurrenceID):
+        &quot;&quot;&quot;
+        Derive an overridden component for the associated RECURRENCE-ID. This assumes
+        that the R-ID is valid for the actual recurrence being used. It also assumes
+        that this component is the master.
+
+        @param recurrenceID: the recurrence instance
+        @type recurrenceID: L{DateTime}
+
+        @return: the derived component
+        @rtype: L{ComponentRecur} or L{None}
+        &quot;&quot;&quot;
+
+        # Create the derived instance
+        newcomp = self.duplicate()
+
+        # Strip out unwanted recurrence properties
+        for propname in (
+            definitions.cICalProperty_RRULE,
+            definitions.cICalProperty_RDATE,
+            definitions.cICalProperty_EXRULE,
+            definitions.cICalProperty_EXDATE,
+            definitions.cICalProperty_RECURRENCE_ID,
+        ):
+            newcomp.removeProperties(propname)
+
+        # New DTSTART is the RECURRENCE-ID we are deriving but adjusted to the
+        # original DTSTART's localtime
+        dtstart = newcomp.getStart()
+        dtend = newcomp.getEnd()
+        oldduration = dtend - dtstart
+
+        newdtstartValue = recurrenceID.duplicate()
+        if not dtstart.isDateOnly():
+            if dtstart.local():
+                newdtstartValue.adjustTimezone(dtstart.getTimezone())
+        else:
+            newdtstartValue.setDateOnly(True)
+
+        newcomp.removeProperties(definitions.cICalProperty_DTSTART)
+        newcomp.removeProperties(definitions.cICalProperty_DTEND)
+        prop = Property(definitions.cICalProperty_DTSTART, newdtstartValue)
+        newcomp.addProperty(prop)
+        if not newcomp.useDuration():
+            prop = Property(definitions.cICalProperty_DTEND, newdtstartValue + oldduration)
+            newcomp.addProperty(prop)
+
+        newcomp.addProperty(Property(&quot;RECURRENCE-ID&quot;, newdtstartValue))
+
+        # After creating/changing a component we need to do this to keep PyCalendar happy
+        newcomp.finalise()
+
+        return newcomp
+
+
</ins><span class="cx">     def createExpanded(self, master, recurid):
</span><span class="cx">         return ComponentExpanded(master, recurid)
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendaricalendarpatchpy"></a>
<div class="addfile"><h4>Added: PyCalendar/branches/patch/src/pycalendar/icalendar/patch.py (0 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/icalendar/patch.py                                (rev 0)
+++ PyCalendar/branches/patch/src/pycalendar/icalendar/patch.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -0,0 +1,714 @@
</span><ins>+##
+#    Copyright (c) 2015 Cyrus Daboo. 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.
+##
+
+import operator
+from urlparse import unquote
+from pycalendar.icalendar.componentrecur import ComponentRecur
+from pycalendar.componentbase import ComponentBase
+from pycalendar.datetime import DateTime
+from pycalendar.icalendar.property import Property
+from pycalendar.icalendar.calendar import Calendar
+
+class PatchDocument(object):
+    &quot;&quot;&quot;
+    Represents an entire patch document by maintaining a list of all its commands.
+    &quot;&quot;&quot;
+
+    def __init__(self, text=None):
+        self.commands = []
+        if text:
+            self.parseText(text)
+
+
+    def parseText(self, text):
+
+        # Split into lines and parse a sequence of commands from each
+        lines = text.splitlines()
+        while lines:
+            command = Command.parseFromText(lines)
+            if command is None:
+                break
+            self.commands.append(command)
+
+        if lines:
+            raise ValueError(&quot;Lines left after parsing commands: {}&quot;.format(lines))
+
+
+    def applyPatch(self, calendar):
+        &quot;&quot;&quot;
+        Apply the patch to the specified calendar. The supplied L{Calendar} object will be
+        changed in place.
+
+        @param calendar: calendar to patch
+        @type calendar: L{Calendar}
+        &quot;&quot;&quot;
+        for command in self.commands:
+            command.applyPatch(calendar)
+
+
+
+class Command(object):
+    &quot;&quot;&quot;
+    Represents a patch document command.
+    &quot;&quot;&quot;
+
+    CREATE = &quot;create&quot;
+    UPDATE = &quot;update&quot;
+    DELETE = &quot;delete&quot;
+    ADD = &quot;add&quot;
+    REMOVE = &quot;remove&quot;
+    ACTIONS = (CREATE, UPDATE, DELETE, ADD, REMOVE)
+
+    def __init__(self):
+        self.action = None
+        self.path = None
+        self.data = None
+
+
+    @classmethod
+    def create(cls, action, path, data=None):
+        if action not in cls.ACTIONS:
+            raise ValueError(&quot;Invalid action: {}&quot;.format(action))
+        if isinstance(path, str):
+            path = Path(path)
+        elif not isinstance(path, Path):
+            raise ValueError(&quot;Invalid path: {}&quot;.format(path))
+        if data is not None and not isinstance(data, str):
+            raise ValueError(&quot;Invalid data: {}&quot;.format(data))
+        if action == Command.DELETE:
+            if data is not None:
+                raise ValueError(&quot;Must not have data for action: {}&quot;.format(action))
+        else:
+            if data is None:
+                raise ValueError(&quot;Must have data for action: {}&quot;.format(action))
+
+        command = Command()
+        command.action = action
+        command.path = path
+        command.data = data
+        return command
+
+
+    @classmethod
+    def parseFromText(cls, lines):
+        &quot;&quot;&quot;
+        Parse a command from a list of text format lines.
+
+        @param lines: lines in the patch document. The lines
+            parsed from the list will be removed from the list.
+        @type lines: L{list}
+
+        @return: L{Command} if a command was parsed, L{None} if not
+        &quot;&quot;&quot;
+
+        # First line must be &quot;&lt;&lt;action&gt;&gt; &lt;&lt;path&gt;&gt;&quot;
+        line = lines.pop(0)
+        action, path = line.split(&quot; &quot;, 1)
+        if action not in Command.ACTIONS:
+            raise ValueError(&quot;Invalid action: {}&quot;.format(line))
+        try:
+            path = Path(path)
+        except ValueError:
+            raise ValueError(&quot;Invalid path: {}&quot;.format(line))
+
+        # All but the &quot;delete&quot; action require data
+        data = None
+        if action != Command.DELETE:
+            data = []
+            while lines:
+                line = lines.pop(0)
+                if line == &quot;.&quot;:
+                    break
+                data.append(line)
+            else:
+                raise ValueError(&quot;Invalid data: {}&quot;.format(data))
+
+        return Command.create(action, path, &quot;\r\n&quot;.join(data) if data else None)
+
+
+    def applyPatch(self, calendar):
+        &quot;&quot;&quot;
+        Apply the patch to the specified calendar. The supplied L{Calendar} object will be
+        changed in place.
+
+        @param calendar: calendar to patch
+        @type calendar: L{Calendar}
+        &quot;&quot;&quot;
+        matching_items = self.path.match(calendar, for_update=(self.action == Command.UPDATE))
+        call = getattr(self, &quot;{}Action&quot;.format(self.action))
+        if call is not None:
+            call(matching_items)
+
+
+    def createAction(self, matches):
+        &quot;&quot;&quot;
+        Execute a create action on the matched items.
+
+        @param matches: list of matched components/properties/parameters
+        @type matches: L{list}
+        &quot;&quot;&quot;
+        if self.path.targetComponent():
+            # Data is a list of components
+            newcomponents = self.componentData()
+            for component in matches:
+                for newcomponent in newcomponents:
+                    component.addComponent(newcomponent.duplicate())
+
+        elif self.path.targetPropertyNoName():
+            # Data is a list of properties
+            newproperties = self.propertyData()
+            for component, _ignore_property in matches:
+                for newproperty in newproperties:
+                    component.addProperty(newproperty.duplicate())
+
+        elif self.path.targetParameterNoName():
+            # Data is a list of parameters
+            newparameters = self.parameterData()
+            for _ignore_component, property, _ignore_parameter_name in matches:
+                for parameter in newparameters:
+                    # Remove existing, then add
+                    property.removeParameters(parameter.getName())
+                    property.addParameter(parameter.duplicate())
+        else:
+            raise ValueError(&quot;create action path is not valid: {}&quot;.format(self.path))
+
+
+    def updateAction(self, matches):
+        &quot;&quot;&quot;
+        Execute an update action on the matched items.
+
+        @param matches: list of matched components/properties/parameters
+        @type matches: L{list}
+        &quot;&quot;&quot;
+
+        if self.path.targetComponent():
+            # First remove matched components and record the parent
+            parent = None
+            for component in matches:
+                parent = component.getParentComponent()
+                component.removeFromParent()
+
+            # Now add new components (from the data) to the parent
+            if parent is not None:
+                newcomponents = self.componentData()
+                for component in matches:
+                    for newcomponent in newcomponents:
+                        parent.addComponent(newcomponent.duplicate())
+
+        elif self.path.targetProperty():
+            # First remove matched properties and record the parent components
+            components = set()
+            for component, property in matches:
+                components.add(component)
+                if property is not None:
+                    component.removeProperty(property)
+
+            # Now add new properties (from the data) to each parent component
+            newproperties = self.propertyData()
+            for component in components:
+                for newproperty in newproperties:
+                    component.addProperty(newproperty.duplicate())
+
+        elif self.path.targetParameterNoName():
+            # First remove matched parameters and record the parent properties
+            properties = set()
+            for _ignore_component, property, parameter_name in matches:
+                properties.add(properties)
+                property.removeParameters(parameter_name)
+
+            # Now add new parameters (from the data) to each parent property
+            newparameters = self.parameterData()
+            for property in properties:
+                for parameter in newparameters:
+                    # Remove existing, then add
+                    property.removeParameters(parameter.getName())
+                    property.addParameter(parameter.duplicate())
+        else:
+            raise ValueError(&quot;update action path is not valid: {}&quot;.format(self.path))
+
+
+    def deleteAction(self, matches):
+        &quot;&quot;&quot;
+        Execute a delete action on the matched items.
+
+        @param matches: list of matched components/properties/parameters
+        @type matches: L{list}
+        &quot;&quot;&quot;
+        if self.path.targetComponent():
+            for component in matches:
+                component.removeFromParent()
+
+        elif self.path.targetProperty():
+            for component, property in matches:
+                component.removeProperty(property)
+
+        elif self.path.targetParameter():
+            for _ignore_component, property, parameter_name in matches:
+                property.removeParameters(parameter_name)
+        else:
+            raise ValueError(&quot;delete action path is not valid: {}&quot;.format(self.path))
+
+
+    def addAction(self, matches):
+        pass
+
+
+    def removeAction(self, matches):
+        pass
+
+
+    def componentData(self):
+        &quot;&quot;&quot;
+        Parse the data item into a list of components.
+
+        @return: list of components
+        @rtype: L{list} of L{Component}
+        &quot;&quot;&quot;
+
+        # Data must be a set of components. Wrap the data inside a VCALENDAR and parse
+        newdata = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:ignore
+{}
+END:VCALENDAR
+&quot;&quot;&quot;.format(self.data)
+        calendar = Calendar.parseText(newdata)
+        return calendar.getComponents()
+
+
+    def propertyData(self):
+        &quot;&quot;&quot;
+        Parse the data item into a list of properties.
+
+        @return: list of components
+        @rtype: L{list} of L{Property}
+        &quot;&quot;&quot;
+        return [Property.parseText(line) for line in self.data.splitlines()]
+
+
+    def parameterData(self):
+        &quot;&quot;&quot;
+        Parse the data item into a list of parameters.
+
+        @return: list of components
+        @rtype: L{list} of L{Parameter}
+        &quot;&quot;&quot;
+
+        # Data must be a sets of parameters. Wrap each set inside a property and then return them all
+        newparameters = []
+        newproperties = [Property.parseText(&quot;X-FOO{}:ignore&quot;.format(line)) for line in self.data.splitlines()]
+        for newproperty in newproperties:
+            for parameters in newproperty.getParameters().values():
+                newparameters.extend(parameters)
+        return newparameters
+
+
+
+class Path(object):
+    &quot;&quot;&quot;
+    A path item used to select one or more iCalendar elements
+    &quot;&quot;&quot;
+
+    def __init__(self, path):
+        &quot;&quot;&quot;
+        Create a L{Path} by parsing a text path.
+
+        @param path: the path to parse
+        @type path: L{str}
+        &quot;&quot;&quot;
+        self.components = []
+        self.property = None
+        self.parameter = None
+        self._parsePath(path)
+
+
+    def targetComponent(self):
+        &quot;&quot;&quot;
+        Indicate whether the path targets a component.
+
+        @return: L{True} for a component target, L{False} otherwise.
+        @rtype: L{bool}
+        &quot;&quot;&quot;
+        return self.property is None
+
+
+    def targetProperty(self):
+        &quot;&quot;&quot;
+        Indicate whether the path targets a property.
+
+        @return: L{True} for a property target, L{False} otherwise.
+        @rtype: L{bool}
+        &quot;&quot;&quot;
+        return (
+            self.property is not None and
+            not self.property.noName() and
+            self.parameter is None
+        )
+
+
+    def targetPropertyNoName(self):
+        &quot;&quot;&quot;
+        Indicate whether the path targets a property.
+
+        @return: L{True} for a property target, L{False} otherwise.
+        @rtype: L{bool}
+        &quot;&quot;&quot;
+        return self.property is not None and self.property.noName()
+
+
+    def targetParameter(self):
+        &quot;&quot;&quot;
+        Indicate whether the path targets a parameter.
+
+        @return: L{True} for a parameter target, L{False} otherwise.
+        @rtype: L{bool}
+        &quot;&quot;&quot;
+        return (
+            self.property is not None and
+            self.parameter is not None and
+            not self.parameter.noName()
+        )
+
+
+    def targetParameterNoName(self):
+        &quot;&quot;&quot;
+        Indicate whether the path targets a parameter.
+
+        @return: L{True} for a parameter target, L{False} otherwise.
+        @rtype: L{bool}
+        &quot;&quot;&quot;
+        return (
+            self.property is not None and
+            self.parameter is not None and
+            self.parameter.noName()
+        )
+
+
+    def _parsePath(self, path):
+        &quot;&quot;&quot;
+        Parse a text path into its constituent segments.
+
+        @param path: the path to parse
+        @type path: L{str}
+        &quot;&quot;&quot;
+
+        segments = path.split(&quot;/&quot;)
+        property_segment = None
+        parameter_segment = None
+        if segments[0] != &quot;&quot;:
+            raise ValueError(&quot;Invalid path: {}&quot;.format(path))
+        del segments[0]
+        if &quot;#&quot; in segments[-1]:
+            segments[-1], property_segment = segments[-1].split(&quot;#&quot;, 1)
+            if &quot;;&quot; in property_segment:
+                property_segment, parameter_segment = property_segment.split(&quot;;&quot;, 1)
+
+        for item in range(len(segments)):
+            self.components.append(Path.ComponentSegment(segments[item]))
+        if property_segment is not None:
+            self.property = Path.PropertySegment(property_segment)
+        if parameter_segment is not None:
+            self.parameter = Path.ParameterSegment(parameter_segment)
+
+
+    class ComponentSegment(object):
+        &quot;&quot;&quot;
+        Represents a component segment of an L{Path}.
+        &quot;&quot;&quot;
+
+        def __init__(self, segment):
+            &quot;&quot;&quot;
+            Create a component segment of a path by parsing the text.
+
+            @param path: the segment to parse
+            @type path: L{str}
+            &quot;&quot;&quot;
+            self.name = None
+            self.uid = None
+            self.rid = None
+            self.rid_value = None
+
+            self._parseSegment(segment)
+
+
+        def __repr__(self):
+            return &quot;&lt;ComponentSegment: {name}[{uid}][{rid}]&quot;.format(
+                name=self.name,
+                uid=self.uid,
+                rid=(self.rid_value if self.rid_value is not None else &quot;*&quot;) if self.rid else None
+            )
+
+
+        def __eq__(self, other):
+            return (self.name == other.name) and \
+                (self.uid == other.uid) and \
+                (self.rid == other.rid) and \
+                (self.rid_value == other.rid_value)
+
+
+        def _parseSegment(self, segment):
+            &quot;&quot;&quot;
+            Parse a component segment of a path into its constituent parts.
+
+            @param path: the segment to parse
+            @type path: L{str}
+            &quot;&quot;&quot;
+            pos = segment.find(&quot;[&quot;)
+            if pos != -1:
+                self.name, segment_rest = segment.split(&quot;[&quot;, 1)
+                segments = segment_rest.split(&quot;[&quot;)
+                if segments[0].startswith(&quot;UID=&quot;) and segments[0][-1] == &quot;]&quot;:
+                    self.uid = unquote(segments[0][4:-1])
+                    del segments[0]
+                if segments and segments[0].startswith(&quot;RECURRENCE-ID=&quot;) and segments[0][-1] == &quot;]&quot;:
+                    rid = unquote(segments[0][14:-1])
+                    try:
+                        self.rid_value = DateTime.parseText(rid) if rid else None
+                    except ValueError:
+                        raise ValueError(&quot;Invalid component match {}&quot;.format(segment))
+                    self.rid = True
+                    del segments[0]
+
+                if segments:
+                    raise ValueError(&quot;Invalid component match {}&quot;.format(segment))
+            else:
+                self.name = segment
+
+            self.name = self.name.upper()
+
+
+        def match(self, items):
+            &quot;&quot;&quot;
+            Returns all sub-components of the components passed in via the L{items} list
+            that match this path.
+
+            @param items: calendar items to match
+            @type items: L{list}
+
+            @return: items matched
+            @rtype: L{list}
+            &quot;&quot;&quot;
+
+            results = []
+            for item in items:
+                assert(isinstance(item, ComponentBase))
+                matches = item.getComponents(self.name)
+                if self.uid and matches:
+                    matches = [item for item in matches if item.getUID() == self.uid]
+                if self.rid and matches:
+                    # self.rid is None if no RECURRENCE-ID= appears in the path.
+                    # self.rid_value is None if RECURRENCE-ID= appears with no value - match the master instance
+                    # Otherwise match the specific self.rid value.
+                    rid_matches = [item for item in matches if isinstance(item, ComponentRecur) and item.getRecurrenceID() == self.rid_value]
+                    if len(rid_matches) == 0:
+                        if self.rid_value:
+                            # Try deriving an instance - fail if cannot
+                            # Need to have the master first
+                            masters = [item for item in matches if isinstance(item, ComponentRecur) and item.getRecurrenceID() is None]
+                            if not masters:
+                                raise ValueError(&quot;No master component for path {}&quot;.format(self))
+                            elif len(masters) &gt; 1:
+                                raise ValueError(&quot;Too many master components for path {}&quot;.format(self))
+                            derived = masters[0].deriveComponent(self.rid_value)
+                            masters[0].getParentComponent().addComponent(derived)
+                            rid_matches.append(derived)
+                    matches = rid_matches
+                results.extend(matches)
+
+            return results
+
+
+    class PropertySegment(object):
+        &quot;&quot;&quot;
+        Represents a property segment of an L{Path}.
+        &quot;&quot;&quot;
+
+        def __init__(self, segment):
+            &quot;&quot;&quot;
+            Create a property segment of a path by parsing the text.
+
+            @param path: the segment to parse
+            @type path: L{str}
+            &quot;&quot;&quot;
+            self.name = None
+            self.matchCondition = None
+            self._parseSegment(segment)
+
+
+        def __repr__(self):
+            return &quot;&lt;PropertySegment: {s.name}[{s.matchCondition}]&quot;.format(s=self)
+
+
+        def __eq__(self, other):
+            return (self.name == other.name) and \
+                (self.matchCondition == other.matchCondition)
+
+
+        def _parseSegment(self, segment):
+            &quot;&quot;&quot;
+            Parse a property segment of a path into its constituent parts.
+
+            @param path: the segment to parse
+            @type path: L{str}
+            &quot;&quot;&quot;
+            if &quot;[&quot; in segment:
+                self.name, segment_rest = segment.split(&quot;[&quot;, 1)
+                matches = segment_rest.split(&quot;[&quot;)
+                if len(matches) != 1:
+                    raise ValueError(&quot;Invalid property match {}&quot;.format(segment))
+                if matches[0][-1] != &quot;]&quot; or len(matches[0]) &lt; 4:
+                    raise ValueError(&quot;Invalid property match {}&quot;.format(segment))
+                if matches[0][0] == &quot;=&quot;:
+                    op = operator.eq
+                elif matches[0][0] == &quot;!&quot;:
+                    op = operator.ne
+                else:
+                    raise ValueError(&quot;Invalid property match {}&quot;.format(segment))
+                self.matchCondition = (unquote(matches[0][1:-1]), op,)
+            else:
+                self.name = segment
+
+
+        def noName(self):
+            return self.name == &quot;&quot;
+
+
+        def match(self, components, for_update):
+            &quot;&quot;&quot;
+            Returns all properties of the components passed in via the L{items} list
+            that match this path.
+
+            @param components: components to match
+            @type components: L{list}
+
+            @return: items matched
+            @rtype: L{list}
+            &quot;&quot;&quot;
+
+            # Empty name is used for create
+            if self.name:
+                results = []
+                for component in components:
+                    assert(isinstance(component, ComponentBase))
+                    if self.matchCondition is not None:
+                        matches = [(component, prop,) for prop in component.getProperties(self.name) if self.matchCondition[1](prop.getValue().getTextValue(), self.matchCondition[0])]
+                    else:
+                        matches = [(component, prop,) for prop in component.getProperties(self.name)]
+                        if len(matches) == 0 and for_update:
+                            # If no property exists, return L{None} so that an update action will add one
+                            matches = [(component, None)]
+                    results.extend(matches)
+            else:
+                results = [(component, None,) for component in components]
+
+            return results
+
+
+    class ParameterSegment(object):
+        &quot;&quot;&quot;
+        Represents a parameter segment of an L{Path}.
+        &quot;&quot;&quot;
+
+        def __init__(self, segment):
+            &quot;&quot;&quot;
+            Create a parameter segment of a path by parsing the text.
+
+            @param path: the segment to parse
+            @type path: L{str}
+            &quot;&quot;&quot;
+            self.name = None
+            self._parseSegment(segment)
+
+
+        def __repr__(self):
+            return &quot;&lt;ParameterSegment: {s.name}&quot;.format(s=self)
+
+
+        def __eq__(self, other):
+            return (self.name == other.name)
+
+
+        def _parseSegment(self, segment):
+            &quot;&quot;&quot;
+            Parse a parameter segment of a path into its constituent parts.
+
+            @param path: the segment to parse
+            @type path: L{str}
+            &quot;&quot;&quot;
+            if &quot;[&quot; in segment:
+                raise ValueError(&quot;Invalid parameter segment {}&quot;.format(segment))
+            else:
+                self.name = segment
+
+
+        def noName(self):
+            return self.name == &quot;&quot;
+
+
+        def match(self, properties):
+            &quot;&quot;&quot;
+            Returns all properties of the components passed in via the L{items} list
+            that match this path, together with the parameter name being targeted.
+
+            @param properties: properties to match
+            @type properties: L{list}
+
+            @return: items matched
+            @rtype: L{list}
+            &quot;&quot;&quot;
+
+            # Empty name is used for create
+            if self.name:
+                results = []
+                for component, property in properties:
+                    assert(isinstance(component, ComponentBase))
+                    assert(isinstance(property, Property))
+                    results.append((component, property, self.name,))
+            else:
+                results = [(component, property, None,) for component, property in properties]
+
+            return results
+
+
+    def match(self, calendar, for_update=False):
+        &quot;&quot;&quot;
+        Return the list of matching items in the specified calendar.
+
+        @param calendar: calendar to match
+        @type calendar: L{Calendar}
+        @param for_update: L{True} if a property match should return an empty
+            result when there is no match item and no matching property
+        @type for_update: L{bool}
+
+        @return: items matched
+        @rtype: L{list}
+        &quot;&quot;&quot;
+
+        # First segment of path is always assumed to be VCALENDAR - we double check that
+        if self.components[0].name != &quot;VCALENDAR&quot; or calendar.getType().upper() != &quot;VCALENDAR&quot;:
+            return []
+
+        # Start with the VCALENDAR object as the initial match
+        results = [calendar]
+        for component_segment in self.components[1:]:
+            results = component_segment.match(results)
+
+        if self.property is not None:
+            results = self.property.match(results, for_update)
+            if self.parameter is not None:
+                results = self.parameter.match(results)
+
+        return results
</ins></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendaricalendarrecurrencepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/icalendar/recurrence.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/icalendar/recurrence.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/icalendar/recurrence.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -543,134 +543,134 @@
</span><span class="cx"> 
</span><span class="cx">     def generate(self, os):
</span><span class="cx">         try:
</span><del>-            os.write(definitions.cICalValue_RECUR_FREQ)
-            os.write(&quot;=&quot;)
</del><ins>+            os.write(self.getText())
+        except:
+            pass
</ins><span class="cx"> 
</span><del>-            if self.mFreq == definitions.eRecurrence_SECONDLY:
-                os.write(definitions.cICalValue_RECUR_SECONDLY)
</del><span class="cx"> 
</span><del>-            elif self.mFreq == definitions.eRecurrence_MINUTELY:
-                os.write(definitions.cICalValue_RECUR_MINUTELY)
</del><ins>+    def getText(self):
+        result = []
+        result.append(definitions.cICalValue_RECUR_FREQ)
+        result.append(&quot;=&quot;)
</ins><span class="cx"> 
</span><del>-            elif self.mFreq == definitions.eRecurrence_HOURLY:
-                os.write(definitions.cICalValue_RECUR_HOURLY)
</del><ins>+        if self.mFreq == definitions.eRecurrence_SECONDLY:
+            result.append(definitions.cICalValue_RECUR_SECONDLY)
</ins><span class="cx"> 
</span><del>-            elif self.mFreq == definitions.eRecurrence_DAILY:
-                os.write(definitions.cICalValue_RECUR_DAILY)
</del><ins>+        elif self.mFreq == definitions.eRecurrence_MINUTELY:
+            result.append(definitions.cICalValue_RECUR_MINUTELY)
</ins><span class="cx"> 
</span><del>-            elif self.mFreq == definitions.eRecurrence_WEEKLY:
-                os.write(definitions.cICalValue_RECUR_WEEKLY)
</del><ins>+        elif self.mFreq == definitions.eRecurrence_HOURLY:
+            result.append(definitions.cICalValue_RECUR_HOURLY)
</ins><span class="cx"> 
</span><del>-            elif self.mFreq == definitions.eRecurrence_MONTHLY:
-                os.write(definitions.cICalValue_RECUR_MONTHLY)
</del><ins>+        elif self.mFreq == definitions.eRecurrence_DAILY:
+            result.append(definitions.cICalValue_RECUR_DAILY)
</ins><span class="cx"> 
</span><del>-            elif self.mFreq == definitions.eRecurrence_YEARLY:
-                os.write(definitions.cICalValue_RECUR_YEARLY)
</del><ins>+        elif self.mFreq == definitions.eRecurrence_WEEKLY:
+            result.append(definitions.cICalValue_RECUR_WEEKLY)
</ins><span class="cx"> 
</span><del>-            if self.mUseCount:
-                os.write(&quot;;&quot;)
-                os.write(definitions.cICalValue_RECUR_COUNT)
-                os.write(&quot;=&quot;)
-                os.write(str(self.mCount))
-            elif self.mUseUntil:
-                os.write(&quot;;&quot;)
-                os.write(definitions.cICalValue_RECUR_UNTIL)
-                os.write(&quot;=&quot;)
-                self.mUntil.generate(os)
</del><ins>+        elif self.mFreq == definitions.eRecurrence_MONTHLY:
+            result.append(definitions.cICalValue_RECUR_MONTHLY)
</ins><span class="cx"> 
</span><del>-            if self.mInterval &gt; 1:
-                os.write(&quot;;&quot;)
-                os.write(definitions.cICalValue_RECUR_INTERVAL)
-                os.write(&quot;=&quot;)
-                os.write(str(self.mInterval))
</del><ins>+        elif self.mFreq == definitions.eRecurrence_YEARLY:
+            result.append(definitions.cICalValue_RECUR_YEARLY)
</ins><span class="cx"> 
</span><del>-            self.generateList(os, definitions.cICalValue_RECUR_BYSECOND, self.mBySeconds)
-            self.generateList(os, definitions.cICalValue_RECUR_BYMINUTE, self.mByMinutes)
-            self.generateList(os, definitions.cICalValue_RECUR_BYHOUR, self.mByHours)
</del><ins>+        if self.mUseCount:
+            result.append(&quot;;&quot;)
+            result.append(definitions.cICalValue_RECUR_COUNT)
+            result.append(&quot;=&quot;)
+            result.append(str(self.mCount))
+        elif self.mUseUntil:
+            result.append(&quot;;&quot;)
+            result.append(definitions.cICalValue_RECUR_UNTIL)
+            result.append(&quot;=&quot;)
+            result.append(self.mUntil.getText())
</ins><span class="cx"> 
</span><del>-            if (self.mByDay is not None) and (len(self.mByDay) != 0):
-                os.write(&quot;;&quot;)
-                os.write(definitions.cICalValue_RECUR_BYDAY)
-                os.write(&quot;=&quot;)
-                comma = False
-                for iter in self.mByDay:
-                    if comma:
-                        os.write(&quot;,&quot;)
-                    comma = True
</del><ins>+        if self.mInterval &gt; 1:
+            result.append(&quot;;&quot;)
+            result.append(definitions.cICalValue_RECUR_INTERVAL)
+            result.append(&quot;=&quot;)
+            result.append(str(self.mInterval))
</ins><span class="cx"> 
</span><del>-                    if iter[0] != 0:
-                        os.write(str(iter[0]))
</del><ins>+        result.append(self.getList(definitions.cICalValue_RECUR_BYSECOND, self.mBySeconds))
+        result.append(self.getList(definitions.cICalValue_RECUR_BYMINUTE, self.mByMinutes))
+        result.append(self.getList(definitions.cICalValue_RECUR_BYHOUR, self.mByHours))
</ins><span class="cx"> 
</span><del>-                    if iter[1] == definitions.eRecurrence_WEEKDAY_SU:
-                        os.write(definitions.cICalValue_RECUR_WEEKDAY_SU)
</del><ins>+        if (self.mByDay is not None) and (len(self.mByDay) != 0):
+            result.append(&quot;;&quot;)
+            result.append(definitions.cICalValue_RECUR_BYDAY)
+            result.append(&quot;=&quot;)
+            comma = False
+            for iter in self.mByDay:
+                if comma:
+                    result.append(&quot;,&quot;)
+                comma = True
</ins><span class="cx"> 
</span><del>-                    elif iter[1] == definitions.eRecurrence_WEEKDAY_MO:
-                        os.write(definitions.cICalValue_RECUR_WEEKDAY_MO)
</del><ins>+                if iter[0] != 0:
+                    result.append(str(iter[0]))
</ins><span class="cx"> 
</span><del>-                    elif iter[1] == definitions.eRecurrence_WEEKDAY_TU:
-                        os.write(definitions.cICalValue_RECUR_WEEKDAY_TU)
</del><ins>+                if iter[1] == definitions.eRecurrence_WEEKDAY_SU:
+                    result.append(definitions.cICalValue_RECUR_WEEKDAY_SU)
</ins><span class="cx"> 
</span><del>-                    elif iter[1] == definitions.eRecurrence_WEEKDAY_WE:
-                        os.write(definitions.cICalValue_RECUR_WEEKDAY_WE)
</del><ins>+                elif iter[1] == definitions.eRecurrence_WEEKDAY_MO:
+                    result.append(definitions.cICalValue_RECUR_WEEKDAY_MO)
</ins><span class="cx"> 
</span><del>-                    elif iter[1] == definitions.eRecurrence_WEEKDAY_TH:
-                        os.write(definitions.cICalValue_RECUR_WEEKDAY_TH)
</del><ins>+                elif iter[1] == definitions.eRecurrence_WEEKDAY_TU:
+                    result.append(definitions.cICalValue_RECUR_WEEKDAY_TU)
</ins><span class="cx"> 
</span><del>-                    elif iter[1] == definitions.eRecurrence_WEEKDAY_FR:
-                        os.write(definitions.cICalValue_RECUR_WEEKDAY_FR)
</del><ins>+                elif iter[1] == definitions.eRecurrence_WEEKDAY_WE:
+                    result.append(definitions.cICalValue_RECUR_WEEKDAY_WE)
</ins><span class="cx"> 
</span><del>-                    elif iter[1] == definitions.eRecurrence_WEEKDAY_SA:
-                        os.write(definitions.cICalValue_RECUR_WEEKDAY_SA)
</del><ins>+                elif iter[1] == definitions.eRecurrence_WEEKDAY_TH:
+                    result.append(definitions.cICalValue_RECUR_WEEKDAY_TH)
</ins><span class="cx"> 
</span><del>-            self.generateList(os, definitions.cICalValue_RECUR_BYMONTHDAY, self.mByMonthDay)
-            self.generateList(os, definitions.cICalValue_RECUR_BYYEARDAY, self.mByYearDay)
-            self.generateList(os, definitions.cICalValue_RECUR_BYWEEKNO, self.mByWeekNo)
-            self.generateList(os, definitions.cICalValue_RECUR_BYMONTH, self.mByMonth)
-            self.generateList(os, definitions.cICalValue_RECUR_BYSETPOS, self.mBySetPos)
</del><ins>+                elif iter[1] == definitions.eRecurrence_WEEKDAY_FR:
+                    result.append(definitions.cICalValue_RECUR_WEEKDAY_FR)
</ins><span class="cx"> 
</span><del>-            # MO is the default so we do not need it
-            if self.mWeekstart != definitions.eRecurrence_WEEKDAY_MO:
-                os.write(&quot;;&quot;)
-                os.write(definitions.cICalValue_RECUR_WKST)
-                os.write(&quot;=&quot;)
</del><ins>+                elif iter[1] == definitions.eRecurrence_WEEKDAY_SA:
+                    result.append(definitions.cICalValue_RECUR_WEEKDAY_SA)
</ins><span class="cx"> 
</span><del>-                if self.mWeekstart == definitions.eRecurrence_WEEKDAY_SU:
-                    os.write(definitions.cICalValue_RECUR_WEEKDAY_SU)
</del><ins>+        result.append(self.getList(definitions.cICalValue_RECUR_BYMONTHDAY, self.mByMonthDay))
+        result.append(self.getList(definitions.cICalValue_RECUR_BYYEARDAY, self.mByYearDay))
+        result.append(self.getList(definitions.cICalValue_RECUR_BYWEEKNO, self.mByWeekNo))
+        result.append(self.getList(definitions.cICalValue_RECUR_BYMONTH, self.mByMonth))
+        result.append(self.getList(definitions.cICalValue_RECUR_BYSETPOS, self.mBySetPos))
</ins><span class="cx"> 
</span><del>-                elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_MO:
-                    os.write(definitions.cICalValue_RECUR_WEEKDAY_MO)
</del><ins>+        # MO is the default so we do not need it
+        if self.mWeekstart != definitions.eRecurrence_WEEKDAY_MO:
+            result.append(&quot;;&quot;)
+            result.append(definitions.cICalValue_RECUR_WKST)
+            result.append(&quot;=&quot;)
</ins><span class="cx"> 
</span><del>-                elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_TU:
-                    os.write(definitions.cICalValue_RECUR_WEEKDAY_TU)
</del><ins>+            if self.mWeekstart == definitions.eRecurrence_WEEKDAY_SU:
+                result.append(definitions.cICalValue_RECUR_WEEKDAY_SU)
</ins><span class="cx"> 
</span><del>-                elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_WE:
-                    os.write(definitions.cICalValue_RECUR_WEEKDAY_WE)
</del><ins>+            elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_MO:
+                result.append(definitions.cICalValue_RECUR_WEEKDAY_MO)
</ins><span class="cx"> 
</span><del>-                elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_TH:
-                    os.write(definitions.cICalValue_RECUR_WEEKDAY_TH)
</del><ins>+            elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_TU:
+                result.append(definitions.cICalValue_RECUR_WEEKDAY_TU)
</ins><span class="cx"> 
</span><del>-                elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_FR:
-                    os.write(definitions.cICalValue_RECUR_WEEKDAY_FR)
</del><ins>+            elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_WE:
+                result.append(definitions.cICalValue_RECUR_WEEKDAY_WE)
</ins><span class="cx"> 
</span><del>-                elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_SA:
-                    os.write(definitions.cICalValue_RECUR_WEEKDAY_SA)
</del><ins>+            elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_TH:
+                result.append(definitions.cICalValue_RECUR_WEEKDAY_TH)
</ins><span class="cx"> 
</span><del>-        except:
-            pass
</del><ins>+            elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_FR:
+                result.append(definitions.cICalValue_RECUR_WEEKDAY_FR)
</ins><span class="cx"> 
</span><ins>+            elif self.mWeekstart == definitions.eRecurrence_WEEKDAY_SA:
+                result.append(definitions.cICalValue_RECUR_WEEKDAY_SA)
</ins><span class="cx"> 
</span><del>-    def generateList(self, os, title, items):
</del><ins>+        return &quot;&quot;.join(result)
</ins><span class="cx"> 
</span><ins>+
+    def getList(self, title, items):
+
</ins><span class="cx">         if (items is not None) and (len(items) != 0):
</span><del>-            os.write(&quot;;&quot;)
-            os.write(title)
-            os.write(&quot;=&quot;)
-            comma = False
-            for e in items:
-                if comma:
-                    os.write(&quot;,&quot;)
-                comma = True
-                os.write(str(e))
</del><ins>+            return &quot;;{}={}&quot;.format(title, &quot;,&quot;.join([str(item) for item in items]))
+        else:
+            return &quot;&quot;
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def writeXML(self, node, namespace):
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendaricalendarrequeststatusvaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/icalendar/requeststatusvalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/icalendar/requeststatusvalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/icalendar/requeststatusvalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -79,9 +79,16 @@
</span><span class="cx"> 
</span><span class="cx">     # os - StringIO object
</span><span class="cx">     def generate(self, os):
</span><del>-        utils.generateTextList(os, self.mValue if len(self.mValue) &lt; 3 or self.mValue[2] else self.mValue[:2])
</del><ins>+        try:
+            os.write(self.getTextValue())
+        except:
+            pass
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getTextValue(self):
+        return utils.getTextList(self.mValue if len(self.mValue) &lt; 3 or self.mValue[2] else self.mValue[:2])
+
+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx">         value = self.getXMLNode(node, namespace)
</span><span class="cx"> 
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendaricalendartestsrrule_examplesjson"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/icalendar/tests/rrule_examples.json (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/icalendar/tests/rrule_examples.json        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/icalendar/tests/rrule_examples.json        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -15,6 +15,14 @@
</span><span class="cx">                 ]
</span><span class="cx">         },
</span><span class="cx">         {
</span><ins>+                &quot;rule&quot;: &quot;FREQ=YEARLY;BYWEEKNO=1&quot;,
+                &quot;start&quot;: &quot;20141130T000000&quot;,
+                &quot;end&quot;: &quot;20160101T000000&quot;,
+                &quot;results&quot;: [
+            &quot;20141229T000000&quot;
+                ]
+        },
+        {
</ins><span class="cx">                 &quot;rule&quot;: &quot;FREQ=MONTHLY&quot;,
</span><span class="cx">                 &quot;start&quot;: &quot;20140140T120000&quot;,
</span><span class="cx">                 &quot;end&quot;: &quot;20150101T000000&quot;,
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendaricalendarteststest_patchpy"></a>
<div class="addfile"><h4>Added: PyCalendar/branches/patch/src/pycalendar/icalendar/tests/test_patch.py (0 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/icalendar/tests/test_patch.py                                (rev 0)
+++ PyCalendar/branches/patch/src/pycalendar/icalendar/tests/test_patch.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -0,0 +1,2237 @@
</span><ins>+# -*- coding: utf-8 -*-
+##
+#    Copyright (c) 2015 Cyrus Daboo. 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 pycalendar.datetime import DateTime
+from pycalendar.icalendar.calendar import Calendar
+from pycalendar.icalendar.patch import Command, Path, PatchDocument
+import operator
+import unittest
+
+class TestPatchDocument(unittest.TestCase):
+
+
+    def _testPatch(self, data):
+
+        for ctr, items in enumerate(data):
+            calendar = Calendar.parseText(items[&quot;before&quot;])
+            patcher = PatchDocument(items[&quot;patch&quot;])
+            patcher.applyPatch(calendar)
+            self.assertEqual(str(calendar), items[&quot;after&quot;].replace(&quot;\n&quot;, &quot;\r\n&quot;), msg=&quot;Failed test #{}: {}\n{}&quot;.format(ctr + 1, items[&quot;title&quot;], str(calendar)))
+
+
+    def test_createComponent_Simple(self):
+        &quot;&quot;&quot;
+        Test that creation of a single component works.
+        &quot;&quot;&quot;
+
+        data = [
+            {
+                &quot;title&quot;: &quot;Add one component to a calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;create /VCALENDAR
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Add two components to a calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:April Fool's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;create /VCALENDAR
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:April Fool's Day
+END:VEVENT
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Add one component to an event&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Event reminder
+TRIGGER:-PT8M
+UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;create /VCALENDAR/VEVENT
+BEGIN:VALARM
+UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
+DESCRIPTION:Event reminder
+TRIGGER:-PT8M
+ACTION:DISPLAY
+END:VALARM
+.
+&quot;&quot;&quot;,
+            },
+        ]
+
+        self._testPatch(data)
+
+
+    def test_createProperty_Simple(self):
+        &quot;&quot;&quot;
+        Test that creation of a single property works.
+        &quot;&quot;&quot;
+
+        data = [
+            {
+                &quot;title&quot;: &quot;Add one property to a calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;create /VCALENDAR#
+CALSCALE:GREGORIAN
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Add two properties to a calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+REFRESH-INTERVAL:10
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;create /VCALENDAR#
+CALSCALE:GREGORIAN
+REFRESH-INTERVAL:10
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Add one property to an event&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:CANCELLED
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;create /VCALENDAR/VEVENT#
+STATUS:CANCELLED
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Add two properties to an event&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+STATUS:CANCELLED
+SUMMARY:New Year's Day
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;create /VCALENDAR/VEVENT#
+STATUS:CANCELLED
+TRANSP:TRANSPARENT
+.
+&quot;&quot;&quot;,
+            },
+        ]
+
+        self._testPatch(data)
+
+
+    def test_createParameter_Simple(self):
+        &quot;&quot;&quot;
+        Test that creation of a single parameter works.
+        &quot;&quot;&quot;
+
+        data = [
+            {
+                &quot;title&quot;: &quot;Add one parameter to a property&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY;LABEL=Party Time!:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;create /VCALENDAR/VEVENT#SUMMARY;
+;LABEL=Party Time!
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Add one parameter to a property with update&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY;LABEL=Holiday:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY;LABEL=Party Time!:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;create /VCALENDAR/VEVENT#SUMMARY;
+;LABEL=Party Time!
+.
+&quot;&quot;&quot;,
+            },
+        ]
+
+        self._testPatch(data)
+
+
+    def test_updateComponent_Simple(self):
+        &quot;&quot;&quot;
+        Test that update of components works.
+        &quot;&quot;&quot;
+
+        data = [
+            {
+                &quot;title&quot;: &quot;Update one component in a calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day - party time
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;update /VCALENDAR/VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day - party time
+END:VEVENT
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Update one, add another component to a calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day - party time
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20030101
+DTSTART;VALUE=DATE:20030101
+DTEND;VALUE=DATE:20030102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+STATUS:CANCELLED
+SUMMARY:New Year's Day - cancelled
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;update /VCALENDAR/VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day - party time
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20030101
+DTSTART;VALUE=DATE:20030101
+DTEND;VALUE=DATE:20030102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+STATUS:CANCELLED
+SUMMARY:New Year's Day - cancelled
+END:VEVENT
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Update one component in a calendar with others present&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day - party time
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20030101
+DTSTART;VALUE=DATE:20030101
+DTEND;VALUE=DATE:20030102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+STATUS:CANCELLED
+SUMMARY:New Year's Day - cancelled
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day - party time
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20030101
+DTSTART;VALUE=DATE:20030101
+DTEND;VALUE=DATE:20030102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day - it is on again!
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;update /VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252][RECURRENCE-ID=20030101]
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20030101
+DTSTART;VALUE=DATE:20030101
+DTEND;VALUE=DATE:20030102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day - it is on again!
+END:VEVENT
+.
+&quot;&quot;&quot;,
+            },
+        ]
+
+        self._testPatch(data)
+
+
+    def test_updateComponent_Recur(self):
+        &quot;&quot;&quot;
+        Test that update of components works.
+        &quot;&quot;&quot;
+
+        data = [
+            {
+                &quot;title&quot;: &quot;Update one property in an instance&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20020102
+DTSTART;VALUE=DATE:20020102
+DTEND;VALUE=DATE:20020103
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day - party time
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;update /VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252][RECURRENCE-ID=20020102]#SUMMARY
+SUMMARY:New Year's Day - party time
+.
+&quot;&quot;&quot;,
+            },
+        ]
+
+        self._testPatch(data)
+
+
+    def test_updateProperty_Simple(self):
+        &quot;&quot;&quot;
+        Test that update of a single property works.
+        &quot;&quot;&quot;
+
+        data = [
+            {
+                &quot;title&quot;: &quot;Update (add) one property in a calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;update /VCALENDAR/VEVENT#TRANSP
+TRANSP:TRANSPARENT
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Update (existing) property in a calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;update /VCALENDAR/VEVENT#TRANSP
+TRANSP:TRANSPARENT
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Update one property in all events&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:April Fool's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+STATUS:CONFIRMED
+SUMMARY:April Fool's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+STATUS:CONFIRMED
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;update /VCALENDAR/VEVENT#STATUS
+STATUS:CONFIRMED
+.
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Update one property in one event&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+STATUS:CANCELLED
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+STATUS:CANCELLED
+SUMMARY:April Fool's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+STATUS:CANCELLED
+SUMMARY:April Fool's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+STATUS:CONFIRMED
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;update /VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252]#STATUS
+STATUS:CONFIRMED
+.
+&quot;&quot;&quot;,
+            },
+        ]
+
+        self._testPatch(data)
+
+
+    def test_deleteComponent_Simple(self):
+        &quot;&quot;&quot;
+        Test that deletion of a single component works.
+        &quot;&quot;&quot;
+
+        data = [
+            {
+                &quot;title&quot;: &quot;Remove one component from single event calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove one component from multi event calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:April Fool's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:April Fool's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252]
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove all components from multi event calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:April Fool's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove one alarm from single event calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
+DESCRIPTION:Event reminder
+TRIGGER:-PT8M
+ACTION:DISPLAY
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT/VALARM
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove one alarm from single multi alarm event calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
+DESCRIPTION:Event reminder
+TRIGGER:-PT8M
+ACTION:DISPLAY
+END:VALARM
+BEGIN:VALARM
+UID:D78F6991-DFDD-491B-8334-FA9BF8E4F11C
+DESCRIPTION:Event reminder
+TRIGGER:-PT8M
+ACTION:DISPLAY
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Event reminder
+TRIGGER:-PT8M
+UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT/VALARM[UID=D78F6991-DFDD-491B-8334-FA9BF8E4F11C]
+&quot;&quot;&quot;,
+            },
+        ]
+
+        self._testPatch(data)
+
+
+    def test_deleteProperty_Simple(self):
+        &quot;&quot;&quot;
+        Test that deletion of a single property works.
+        &quot;&quot;&quot;
+
+        data = [
+            {
+                &quot;title&quot;: &quot;Remove one property from VCALENDAR&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR#CALSCALE
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove one property from VEVENT&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT#RRULE
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove one property from multi event calendar&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:DFAEA248-AC4E-44D6-8FA3-ACAAA7BA7943
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:April Fool's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:DFAEA248-AC4E-44D6-8FA3-ACAAA7BA7943
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:April Fool's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252]#RRULE
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove one of many properties from VEVENT&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT#ATTENDEE[=mailto:user03@example.com]
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove one of many properties from one of many VEVENTs&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20120101
+DTSTART;VALUE=DATE:20120101
+DTEND;VALUE=DATE:20120102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20120101
+DTSTART;VALUE=DATE:20120101
+DTEND;VALUE=DATE:20120102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT[RECURRENCE-ID=20120101]#ATTENDEE[=mailto:user03@example.com]
+&quot;&quot;&quot;,
+            },
+        ]
+
+        self._testPatch(data)
+
+
+    def test_deleteParameter_Simple(self):
+        &quot;&quot;&quot;
+        Test that deletion of a single parameter works.
+        &quot;&quot;&quot;
+
+        data = [
+            {
+                &quot;title&quot;: &quot;Remove parameter from all properties in VEVENT&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+ATTENDEE:mailto:user03@example.com
+ATTENDEE:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT#ATTENDEE;PARTSTAT
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove parameter from one property in VEVENT&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT#ATTENDEE[=mailto:user03@example.com];PARTSTAT
+&quot;&quot;&quot;,
+            },
+            {
+                &quot;title&quot;: &quot;Remove one parameter from one of many parameters from one of many VEVENTs&quot;,
+                &quot;before&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20120101
+DTSTART;VALUE=DATE:20120101
+DTEND;VALUE=DATE:20120102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;after&quot;: &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID;VALUE=DATE:20120101
+DTSTART;VALUE=DATE:20120101
+DTEND;VALUE=DATE:20120102
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02@example.com
+ATTENDEE:mailto:user03@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user04@example.com
+DTSTAMP:20020101T000000Z
+ORGANIZER:mailto:user01@example.com
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;patch&quot;: &quot;&quot;&quot;delete /VCALENDAR/VEVENT[RECURRENCE-ID=20120101]#ATTENDEE[=mailto:user03@example.com];PARTSTAT
+&quot;&quot;&quot;,
+            },
+        ]
+
+        self._testPatch(data)
+
+
+
+class TestCommand(unittest.TestCase):
+
+    def testCreate(self):
+        test_data = (
+            # Valid
+            (Command.CREATE, &quot;/VCALENDAR&quot;, &quot;BEGIN:VEVENT\r\nEND:VEVENT\r\n&quot;, True,),
+            (Command.CREATE, Path(&quot;/VCALENDAR&quot;), &quot;BEGIN:VEVENT\r\nEND:VEVENT\r\n&quot;, True,),
+            (Command.UPDATE, &quot;/VCALENDAR#VERSION&quot;, &quot;:2.0\r\n&quot;, True,),
+            (Command.UPDATE, Path(&quot;/VCALENDAR#VERSION&quot;), &quot;:2.0\r\n&quot;, True,),
+            (Command.DELETE, &quot;/VCALENDAR#VERSION&quot;, None, True,),
+
+            # Invalid
+            (&quot;foo&quot;, &quot;/VCALENDAR&quot;, &quot;BEGIN:VEVENT\r\nEND:VEVENT\r\n&quot;, False,),
+            (Command.CREATE, 1, &quot;BEGIN:VEVENT\r\nEND:VEVENT\r\n&quot;, False,),
+            (Command.CREATE, &quot;/VCALENDAR&quot;, 1, False,),
+            (Command.CREATE, &quot;/VCALENDAR&quot;, None, False,),
+            (Command.UPDATE, &quot;/VCALENDAR#VERSION&quot;, None, False,),
+            (Command.DELETE, &quot;/VCALENDAR#VERSION&quot;, &quot;:2.0\r\n&quot;, False,),
+        )
+
+        for action, path, data, valid in test_data:
+            try:
+                command = Command.create(action, path, data)
+            except ValueError:
+                self.assertFalse(valid)
+            else:
+                self.assertTrue(valid)
+                self.assertTrue(isinstance(command, Command))
+
+
+    def testParseLines(self):
+        test_data = (
+            # Valid
+            (&quot;&quot;&quot;create /VCALENDAR
+BEGIN:VEVENT
+END:VEVENT
+.
+&quot;&quot;&quot;, True,),
+            (&quot;&quot;&quot;update /VCALENDAR#VERSION
+:2.0
+.
+&quot;&quot;&quot;, True,),
+            (&quot;&quot;&quot;delete /VCALENDAR#VERSION
+&quot;&quot;&quot;, True,),
+
+            # Invalid
+            (&quot;&quot;&quot;foo /VCALENDAR
+BEGIN:VEVENT
+END:VEVENT
+.
+&quot;&quot;&quot;, False,),
+            (&quot;&quot;&quot;create 1
+BEGIN:VEVENT
+END:VEVENT
+.
+&quot;&quot;&quot;, False,),
+            (&quot;&quot;&quot;create /VCALENDAR
+&quot;&quot;&quot;, False,),
+            (&quot;&quot;&quot;create /VCALENDAR
+foo
+bar
+&quot;&quot;&quot;, False,),
+            (&quot;&quot;&quot;update /VCALENDAR
+&quot;&quot;&quot;, False,),
+            (&quot;&quot;&quot;update /VCALENDAR
+foo
+bar
+&quot;&quot;&quot;, False,),
+        )
+
+        for txt, valid in test_data:
+            try:
+                command = Command.parseFromText(txt.splitlines())
+            except ValueError:
+                self.assertFalse(valid, msg=txt)
+            else:
+                self.assertTrue(valid, msg=txt)
+                self.assertTrue(isinstance(command, Command))
+
+
+
+class TestPath(unittest.TestCase):
+
+    test_data = (
+        # Valid
+
+        # Components
+        (
+            &quot;/VCALENDAR&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+            ],
+            None,
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT&quot;),
+            ],
+            None,
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234]&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234]&quot;),
+            ],
+            None,
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234%2F4567]&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234/4567]&quot;),
+            ],
+            None,
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]&quot;),
+            ],
+            None,
+            None,
+        ),
+
+        # Properties
+        (
+            &quot;/VCALENDAR#VERSION&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+            ],
+            Path.PropertySegment(&quot;VERSION&quot;),
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT#SUMMARY&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT&quot;),
+            ],
+            Path.PropertySegment(&quot;SUMMARY&quot;),
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT#SUMMARY[=abc]&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT&quot;),
+            ],
+            Path.PropertySegment(&quot;SUMMARY[=abc]&quot;),
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT#SUMMARY[=a%2Fc]&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT&quot;),
+            ],
+            Path.PropertySegment(&quot;SUMMARY[=a/c]&quot;),
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT#SUMMARY[!abc]&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT&quot;),
+            ],
+            Path.PropertySegment(&quot;SUMMARY[!abc]&quot;),
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234]#SUMMARY&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234]&quot;),
+            ],
+            Path.PropertySegment(&quot;SUMMARY&quot;),
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234]#SUMMARY[=abc]&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234]&quot;),
+            ],
+            Path.PropertySegment(&quot;SUMMARY[=abc]&quot;),
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]#SUMMARY&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]&quot;),
+            ],
+            Path.PropertySegment(&quot;SUMMARY&quot;),
+            None,
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]#SUMMARY[=abc]&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]&quot;),
+            ],
+            Path.PropertySegment(&quot;SUMMARY[=abc]&quot;),
+            None,
+        ),
+
+        # Parameters
+        (
+            &quot;/VCALENDAR#VERSION;VALUE&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+            ],
+            Path.PropertySegment(&quot;VERSION&quot;),
+            Path.ParameterSegment(&quot;VALUE&quot;),
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT#ATTENDEE;PARTSTAT&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT&quot;),
+            ],
+            Path.PropertySegment(&quot;ATTENDEE&quot;),
+            Path.ParameterSegment(&quot;PARTSTAT&quot;),
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT#ATTENDEE[=abc];PARTSTAT&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT&quot;),
+            ],
+            Path.PropertySegment(&quot;ATTENDEE[=abc]&quot;),
+            Path.ParameterSegment(&quot;PARTSTAT&quot;),
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT#ATTENDEE[=a%2Fc];PARTSTAT&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT&quot;),
+            ],
+            Path.PropertySegment(&quot;ATTENDEE[=a/c]&quot;),
+            Path.ParameterSegment(&quot;PARTSTAT&quot;),
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT#ATTENDEE[!abc];PARTSTAT&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT&quot;),
+            ],
+            Path.PropertySegment(&quot;ATTENDEE[!abc]&quot;),
+            Path.ParameterSegment(&quot;PARTSTAT&quot;),
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234]#ATTENDEE;PARTSTAT&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234]&quot;),
+            ],
+            Path.PropertySegment(&quot;ATTENDEE&quot;),
+            Path.ParameterSegment(&quot;PARTSTAT&quot;),
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234]#ATTENDEE[=abc];PARTSTAT&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234]&quot;),
+            ],
+            Path.PropertySegment(&quot;ATTENDEE[=abc]&quot;),
+            Path.ParameterSegment(&quot;PARTSTAT&quot;),
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]#ATTENDEE;PARTSTAT&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]&quot;),
+            ],
+            Path.PropertySegment(&quot;ATTENDEE&quot;),
+            Path.ParameterSegment(&quot;PARTSTAT&quot;),
+        ),
+        (
+            &quot;/VCALENDAR/VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]#ATTENDEE[=abc];PARTSTAT&quot;,
+            True,
+            [
+                Path.ComponentSegment(&quot;VCALENDAR&quot;),
+                Path.ComponentSegment(&quot;VEVENT[UID=1234][RECURRENCE-ID=20150907T120000Z]&quot;),
+            ],
+            Path.PropertySegment(&quot;ATTENDEE[=abc]&quot;),
+            Path.ParameterSegment(&quot;PARTSTAT&quot;),
+        ),
+
+        # Invalid
+    )
+
+    def testParse(self):
+
+        for strpath, valid, components, property, parameter in TestPath.test_data:
+            try:
+                path = Path(strpath)
+            except ValueError:
+                self.assertFalse(valid)
+            else:
+                self.assertTrue(valid)
+                self.assertEqual(path.components, components)
+                self.assertEqual(path.property, property)
+                self.assertEqual(path.parameter, parameter)
+
+
+    def testType(self):
+
+        data = [
+            (&quot;/VCALENDAR&quot;, True, False, False, False, False,),
+            (&quot;/VCALENDAR/VEVENT&quot;, True, False, False, False, False,),
+            (&quot;/VCALENDAR/VEVENT#SUMMARY&quot;, False, True, False, False, False,),
+            (&quot;/VCALENDAR/VEVENT#&quot;, False, False, True, False, False,),
+            (&quot;/VCALENDAR/VEVENT#SUMMARY;X-PARAM&quot;, False, False, False, True, False,),
+            (&quot;/VCALENDAR/VEVENT#SUMMARY;&quot;, False, False, False, False, True,),
+        ]
+
+        for strpath, isComponent, isProperty, isPropertyNoName, isParameter, isParameterNoName in data:
+            path = Path(strpath)
+            self.assertEqual(path.targetComponent(), isComponent)
+            self.assertEqual(path.targetProperty(), isProperty)
+            self.assertEqual(path.targetPropertyNoName(), isPropertyNoName)
+            self.assertEqual(path.targetParameter(), isParameter)
+            self.assertEqual(path.targetParameterNoName(), isParameterNoName)
+
+
+    def testMatch_Components_Simple(self):
+
+        icalendar = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        calendar = Calendar.parseText(icalendar.replace(&quot;\n&quot;, &quot;\r\n&quot;))
+        path = Path(&quot;/VCALENDAR&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertIs(matched[0], calendar)
+
+        path = Path(&quot;/VCALENDAR/VEVENT&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertIs(matched[0], calendar.getComponents(&quot;VEVENT&quot;)[0])
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=123]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertIs(matched[0], calendar.getComponents(&quot;VEVENT&quot;)[0])
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=123][RECURRENCE-ID=20150101T000000Z]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252][RECURRENCE-ID=]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertIs(matched[0], calendar.getComponents(&quot;VEVENT&quot;)[0])
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=123][RECURRENCE-ID=]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252][RECURRENCE-ID=20020101]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252][RECURRENCE-ID=]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertIs(matched[0], calendar.getComponents(&quot;VEVENT&quot;)[0])
+
+
+    def testMatch_Components_Multiple(self):
+
+        icalendar = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:165EF135-BA92-435A-88C9-562F95030908
+DTSTART;VALUE=DATE:20020401
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:April Fool's Day
+END:VEVENT
+BEGIN:VEVENT
+UID:5EA5AF47-77F5-4EEE-9944-69651C97755B
+DTSTART;VALUE=DATE:20020921
+DURATION:P1D
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY
+SUMMARY:Birthday
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        calendar = Calendar.parseText(icalendar.replace(&quot;\n&quot;, &quot;\r\n&quot;))
+        components_by_uid = dict([(component.getUID(), component) for component in calendar.getComponents()])
+
+        path = Path(&quot;/VCALENDAR&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertIs(matched[0], calendar)
+
+        path = Path(&quot;/VCALENDAR/VEVENT&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 3)
+        self.assertEqual(
+            set([item.getUID() for item in matched]),
+            set(components_by_uid.keys()),
+        )
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=123]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        for key in components_by_uid.keys():
+            path = Path(&quot;/VCALENDAR/VEVENT[UID={key}]&quot;.format(key=key))
+            matched = path.match(calendar)
+            self.assertEqual(len(matched), 1)
+            self.assertIs(matched[0], components_by_uid[key])
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=123][RECURRENCE-ID=20150101T000000Z]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252][RECURRENCE-ID=]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            components_by_uid[&quot;C3184A66-1ED0-11D9-A5E0-000A958A3252&quot;],
+        )
+
+        for key in components_by_uid.keys():
+            path = Path(&quot;/VCALENDAR/VEVENT[UID={key}][RECURRENCE-ID=20150101T000000Z]&quot;.format(key=key))
+            matched = path.match(calendar)
+            self.assertEqual(len(matched), 1)
+
+        for key in components_by_uid.keys():
+            path = Path(&quot;/VCALENDAR/VEVENT[UID={key}][RECURRENCE-ID=]&quot;.format(key=key))
+            matched = path.match(calendar)
+            self.assertEqual(len(matched), 1)
+            self.assertIs(matched[0], components_by_uid[key])
+
+
+    def testMatch_Components_Recurring(self):
+
+        icalendar = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART:20020101T120000Z
+DURATION:PT1H
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=DAILY
+SUMMARY:Meeting
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID:20020102T120000Z
+DTSTART:20020102T130000Z
+DURATION:PT1H
+DTSTAMP:20020101T000000Z
+SUMMARY:Meeting #2
+END:VEVENT
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+RECURRENCE-ID:20020103T120000Z
+DTSTART:20020103T140000Z
+DURATION:PT1H
+DTSTAMP:20020101T000000Z
+SUMMARY:Meeting #3
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        calendar = Calendar.parseText(icalendar.replace(&quot;\n&quot;, &quot;\r\n&quot;))
+        components_by_rid = dict([(component.getRecurrenceID(), component) for component in calendar.getComponents()])
+
+        path = Path(&quot;/VCALENDAR&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertIs(matched[0], calendar)
+
+        path = Path(&quot;/VCALENDAR/VEVENT&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 3)
+        self.assertEqual(
+            set([item.getRecurrenceID() for item in matched]),
+            set(components_by_rid.keys()),
+        )
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=123][RECURRENCE-ID=20150101T000000Z]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        for key in components_by_rid.keys():
+            path = Path(&quot;/VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252][RECURRENCE-ID={key}]&quot;.format(key=key if key else &quot;&quot;))
+            matched = path.match(calendar)
+            self.assertEqual(len(matched), 1)
+            self.assertIs(matched[0], components_by_rid[key])
+
+        path = Path(&quot;/VCALENDAR/VEVENT[UID=C3184A66-1ED0-11D9-A5E0-000A958A3252][RECURRENCE-ID=]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertIs(matched[0], components_by_rid[None])
+
+
+    def testMatch_Properties_Simple(self):
+
+        icalendar = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        calendar = Calendar.parseText(icalendar.replace(&quot;\n&quot;, &quot;\r\n&quot;))
+        path = Path(&quot;/VCALENDAR#VERSION&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar, calendar.getProperties(&quot;VERSION&quot;)[0],),
+        )
+
+        path = Path(&quot;/VCALENDAR#&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar, None,),
+        )
+
+        path = Path(&quot;/VCALENDAR#FOOBAR&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT#SUMMARY&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar.getComponents()[0], calendar.getComponents()[0].getProperties(&quot;SUMMARY&quot;)[0],),
+        )
+
+        path = Path(&quot;/VCALENDAR/VEVENT#&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar.getComponents()[0], None,),
+        )
+
+        # Non-existent - for_update changes behavior
+        path = Path(&quot;/VCALENDAR/VEVENT#FOOBAR&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT#FOOBAR&quot;)
+        matched = path.match(calendar, for_update=True)
+        self.assertEqual(len(matched), 1)
+
+        path = Path(&quot;/VCALENDAR/VEVENT#SUMMARY[=New Year's Day]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar.getComponents()[0], calendar.getComponents()[0].getProperties(&quot;SUMMARY&quot;)[0],),
+        )
+
+        # Non-existent - for_update does not change behavior
+        path = Path(&quot;/VCALENDAR/VEVENT#SUMMARY[=New Years Day]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT#SUMMARY[=New Years Day]&quot;)
+        matched = path.match(calendar, for_update=True)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT#DTSTART[=20020101]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar.getComponents()[0], calendar.getComponents()[0].getProperties(&quot;DTSTART&quot;)[0],),
+        )
+
+        path = Path(&quot;/VCALENDAR/VEVENT#RRULE[=20020101]&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+
+    def testMatch_Parameters_Simple(self):
+
+        icalendar = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//mulberrymail.com//Mulberry v4.0//EN
+BEGIN:VEVENT
+UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTART;VALUE=DATE:20020101
+DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T000000Z
+RRULE:FREQ=YEARLY;UNTIL=20031231;BYMONTH=1
+SUMMARY:New Year's Day
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        calendar = Calendar.parseText(icalendar.replace(&quot;\n&quot;, &quot;\r\n&quot;))
+
+        path = Path(&quot;/VCALENDAR/VEVENT#SUMMARY;X-PARAM&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar.getComponents()[0], calendar.getComponents()[0].getProperties(&quot;SUMMARY&quot;)[0], &quot;X-PARAM&quot;,)
+        )
+
+        path = Path(&quot;/VCALENDAR/VEVENT#SUMMARY;&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar.getComponents()[0], calendar.getComponents()[0].getProperties(&quot;SUMMARY&quot;)[0], None,)
+        )
+
+        path = Path(&quot;/VCALENDAR/VEVENT#FOOBAR;X-PARAM&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT#SUMMARY[=New Year's Day];X-PARAM&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar.getComponents()[0], calendar.getComponents()[0].getProperties(&quot;SUMMARY&quot;)[0], &quot;X-PARAM&quot;,)
+        )
+
+        path = Path(&quot;/VCALENDAR/VEVENT#SUMMARY[=New Years Day];X-PARAM&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 0)
+
+        path = Path(&quot;/VCALENDAR/VEVENT#DTSTART[=20020101];VALUE&quot;)
+        matched = path.match(calendar)
+        self.assertEqual(len(matched), 1)
+        self.assertEqual(
+            matched[0],
+            (calendar.getComponents()[0], calendar.getComponents()[0].getProperties(&quot;DTSTART&quot;)[0], &quot;VALUE&quot;,)
+        )
+
+
+
+class TestComponentSegment(unittest.TestCase):
+
+    test_data = (
+        # Valid
+        (&quot;VCALENDAR&quot;, True, &quot;VCALENDAR&quot;, None, None, None,),
+        (&quot;VCALENDAR[UID=1234]&quot;, True, &quot;VCALENDAR&quot;, &quot;1234&quot;, None, None,),
+        (&quot;VCALENDAR[UID=1234%2F4567]&quot;, True, &quot;VCALENDAR&quot;, &quot;1234/4567&quot;, None, None,),
+        (&quot;VCALENDAR[UID=1234][RECURRENCE-ID=]&quot;, True, &quot;VCALENDAR&quot;, &quot;1234&quot;, True, None,),
+        (&quot;VCALENDAR[UID=1234][RECURRENCE-ID=20150907T120000Z]&quot;, True, &quot;VCALENDAR&quot;, &quot;1234&quot;, True, &quot;20150907T120000Z&quot;,),
+
+        # Invalid
+        (&quot;VCALENDAR[]&quot;, False, None, None, None, None,),
+        (&quot;VCALENDAR[foo]&quot;, False, None, None, None, None,),
+        (&quot;VCALENDAR[foo=bar]&quot;, False, None, None, None, None,),
+        (&quot;VCALENDAR[UID=&quot;, False, None, None, None, None,),
+        (&quot;VCALENDAR[UID=1234][]&quot;, False, None, None, None, None,),
+        (&quot;VCALENDAR[UID=1234][foo=bar]&quot;, False, None, None, None, None,),
+        (&quot;VCALENDAR[UID=1234][RECURRENCE-ID=&quot;, False, None, None, None, None,),
+    )
+
+    def testParse(self):
+
+        for segment, valid, name, uid, rid, rid_value in TestComponentSegment.test_data:
+            try:
+                component = Path.ComponentSegment(segment)
+            except ValueError:
+                self.assertFalse(valid)
+            else:
+                self.assertTrue(valid)
+                self.assertEqual(component.name, name)
+                self.assertEqual(component.uid, uid)
+                self.assertEqual(component.rid, rid)
+                self.assertEqual(component.rid_value, DateTime.parseText(rid_value) if rid_value else None)
+
+
+
+class TestPropertySegment(unittest.TestCase):
+
+    test_data = (
+        # Valid
+        (&quot;STATUS&quot;, True, &quot;STATUS&quot;, None,),
+        (&quot;STATUS[=COMPLETED]&quot;, True, &quot;STATUS&quot;, (&quot;COMPLETED&quot;, operator.eq,),),
+        (&quot;STATUS[!COMPLETED]&quot;, True, &quot;STATUS&quot;, (&quot;COMPLETED&quot;, operator.ne,),),
+        (&quot;SUMMARY[=a%2Fb]&quot;, True, &quot;SUMMARY&quot;, (&quot;a/b&quot;, operator.eq,),),
+        (&quot;&quot;, True, &quot;&quot;, None,),
+
+        # Invalid
+        (&quot;STATUS[]&quot;, False, None, None,),
+        (&quot;STATUS[foo]&quot;, False, None, None,),
+        (&quot;STATUS[=]&quot;, False, None, None,),
+        (&quot;STATUS[=COMPLETED&quot;, False, None, None,),
+        (&quot;STATUS[=COMPLETED][=FAILED]&quot;, False, None, None,),
+    )
+
+    def testParse(self):
+
+        for segment, valid, name, matchCondition in TestPropertySegment.test_data:
+            try:
+                property = Path.PropertySegment(segment)
+            except ValueError:
+                self.assertFalse(valid)
+            else:
+                self.assertTrue(valid)
+                self.assertEqual(property.name, name)
+                self.assertEqual(property.matchCondition, matchCondition)
+
+
+
+class TestParameterSegment(unittest.TestCase):
+
+    test_data = (
+        # Valid
+        (&quot;PARTSTAT&quot;, True, &quot;PARTSTAT&quot;,),
+        (&quot;&quot;, True, &quot;&quot;,),
+
+        # Invalid
+        (&quot;PARTSTAT[]&quot;, False, None,),
+        (&quot;PARTSTAT[&quot;, False, None,),
+        (&quot;PARTSTAT[=NEEDS-ACTION]&quot;, False, None,),
+    )
+
+    def testParse(self):
+
+        for segment, valid, name in TestParameterSegment.test_data:
+            try:
+                property = Path.ParameterSegment(segment)
+            except ValueError:
+                self.assertFalse(valid)
+            else:
+                self.assertTrue(valid)
+                self.assertEqual(property.name, name)
</ins></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendaricalendarteststest_recurrencepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/icalendar/tests/test_recurrence.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/icalendar/tests/test_recurrence.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/icalendar/tests/test_recurrence.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -208,7 +208,7 @@
</span><span class="cx">             self.assertEqual(
</span><span class="cx">                 items,
</span><span class="cx">                 results,
</span><del>-                msg=&quot;Failed rule: #{} {}&quot;.format(ctr + 1, i[&quot;rule&quot;])
</del><ins>+                msg=&quot;Failed rule: #{} {} {}&quot;.format(ctr + 1, i[&quot;rule&quot;], items)
</ins><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarintegervaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/integervalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/integervalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/integervalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -40,11 +40,15 @@
</span><span class="cx">     # os - StringIO object
</span><span class="cx">     def generate(self, os):
</span><span class="cx">         try:
</span><del>-            os.write(str(self.mValue))
</del><ins>+            os.write(self.getTextValue())
</ins><span class="cx">         except:
</span><span class="cx">             pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getTextValue(self):
+        return str(self.mValue)
+
+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx">         value = self.getXMLNode(node, namespace)
</span><span class="cx">         value.text = str(self.mValue)
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarmultivaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/multivalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/multivalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/multivalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -78,17 +78,15 @@
</span><span class="cx"> 
</span><span class="cx">     def generate(self, os):
</span><span class="cx">         try:
</span><del>-            first = True
-            for iter in self.mValues:
-                if first:
-                    first = False
-                else:
-                    os.write(&quot;,&quot;)
-                iter.generate(os)
</del><ins>+            os.write(self.getTextValue())
</ins><span class="cx">         except:
</span><span class="cx">             pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getTextValue(self):
+        return &quot;,&quot;.join([value.getText() for value in self.mValues])
+
+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx">         for iter in self.mValues:
</span><span class="cx">             iter.writeXML(node, namespace)
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarparserpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/parser.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/parser.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/parser.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -79,4 +79,3 @@
</span><span class="cx">         ParserContext.INVALID_REQUEST_STATUS_VALUE = ParserContext.PARSER_RAISE
</span><span class="cx">         ParserContext.BACKSLASH_IN_URI_VALUE = ParserContext.PARSER_RAISE
</span><span class="cx">         ParserContext.BACKSLASH_IN_GEO_VALUE = ParserContext.PARSER_RAISE
</span><del>-        ParserContext.INVALID_REQUEST_STATUS = ParserContext.PARSER_RAISE
</del></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarperiodpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/period.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/period.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/period.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -104,16 +104,18 @@
</span><span class="cx"> 
</span><span class="cx">     def generate(self, os):
</span><span class="cx">         try:
</span><del>-            self.mStart.generate(os)
-            os.write(&quot;/&quot;)
-            if self.mUseDuration:
-                self.mDuration.generate(os)
-            else:
-                self.mEnd.generate(os)
</del><ins>+            os.write(self.getText())
</ins><span class="cx">         except:
</span><span class="cx">             pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getText(self):
+        return &quot;{}/{}&quot;.format(
+            self.mStart.getText(),
+            self.mDuration.getText() if self.mUseDuration else self.mEnd.getText(),
+        )
+
+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx">         start = XML.SubElement(node, xmlutils.makeTag(namespace, xmldefinitions.period_start))
</span><span class="cx">         start.text = self.mStart.getXMLText()
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarplaintextvaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/plaintextvalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/plaintextvalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/plaintextvalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -42,6 +42,10 @@
</span><span class="cx">             pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getTextValue(self):
+        return self.mValue
+
+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx">         value = self.getXMLNode(node, namespace)
</span><span class="cx">         value.text = self.mValue
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendartextvaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/textvalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/textvalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/textvalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -34,9 +34,14 @@
</span><span class="cx">     # os - StringIO object
</span><span class="cx">     def generate(self, os):
</span><span class="cx">         try:
</span><del>-            # Encoding required
-            utils.writeTextValue(os, self.mValue)
</del><ins>+            os.write(self.getTextValue())
</ins><span class="cx">         except:
</span><span class="cx">             pass
</span><span class="cx"> 
</span><ins>+
+    def getTextValue(self):
+        # Encoding required
+        return utils.getTextValue(self.mValue)
+
+
</ins><span class="cx"> Value.registerType(Value.VALUETYPE_TEXT, TextValue, xmldefinitions.value_text)
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarurivaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/urivalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/urivalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/urivalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -43,13 +43,18 @@
</span><span class="cx">         Handle a client bug where it sometimes includes a \n in the value and we need
</span><span class="cx">         to make sure that gets encoded rather than included literally which would break syntax.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+        try:
+            os.write(self.getTextValue())
+        except:
+            pass
+
+
+    def getTextValue(self):
</ins><span class="cx">         if '\n' in self.mValue:
</span><del>-            try:
-                # No encoding required
-                os.write(self.mValue.replace(&quot;\n&quot;, &quot;\\n&quot;))
-            except:
-                pass
</del><ins>+            # No encoding required
+            return self.mValue.replace(&quot;\n&quot;, &quot;\\n&quot;)
</ins><span class="cx">         else:
</span><del>-            super(URIValue, self).generate(os)
</del><ins>+            return super(URIValue, self).getTextValue()
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> Value.registerType(Value.VALUETYPE_URI, URIValue, xmldefinitions.value_uri)
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarutcoffsetvaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/utcoffsetvalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/utcoffsetvalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/utcoffsetvalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -70,26 +70,29 @@
</span><span class="cx">     # os - StringIO object
</span><span class="cx">     def generate(self, os, fullISO=False):
</span><span class="cx">         try:
</span><del>-            abs_value = self.mValue
-            if abs_value &lt; 0 :
-                sign = &quot;-&quot;
-                abs_value = -self.mValue
-            else:
-                sign = &quot;+&quot;
</del><ins>+            os.write(self.getTextValue(fullISO))
+        except:
+            pass
</ins><span class="cx"> 
</span><del>-            secs = abs_value % 60
-            mins = (abs_value / 60) % 60
-            hours = abs_value / (60 * 60)
</del><span class="cx"> 
</span><del>-            s = (&quot;%s%02d:%02d&quot; if fullISO else &quot;%s%02d%02d&quot;) % (sign, hours, mins,)
-            if (secs != 0):
-                s = (&quot;%s:%02d&quot; if fullISO else &quot;%s%02d&quot;) % (s, secs,)
</del><ins>+    def getTextValue(self, fullISO=False):
+        abs_value = self.mValue
+        if abs_value &lt; 0 :
+            sign = &quot;-&quot;
+            abs_value = -self.mValue
+        else:
+            sign = &quot;+&quot;
</ins><span class="cx"> 
</span><del>-            os.write(s)
-        except:
-            pass
</del><ins>+        secs = abs_value % 60
+        mins = (abs_value / 60) % 60
+        hours = abs_value / (60 * 60)
</ins><span class="cx"> 
</span><ins>+        s = (&quot;%s%02d:%02d&quot; if fullISO else &quot;%s%02d%02d&quot;) % (sign, hours, mins,)
+        if (secs != 0):
+            s = (&quot;%s:%02d&quot; if fullISO else &quot;%s%02d&quot;) % (s, secs,)
+        return s
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def writeXML(self, node, namespace):
</span><span class="cx"> 
</span><span class="cx">         os = StringIO()
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarutilspy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/utils.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/utils.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/utils.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -89,51 +89,43 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def escapeTextValue(value):
-    os = StringIO.StringIO()
-    writeTextValue(os, value)
-    return os.getvalue()
</del><ins>+def getTextValue(value):
+    result = []
+    start_pos = 0
+    end_pos = find_first_of(value, &quot;\r\n;\\,&quot;, start_pos)
+    if end_pos != -1:
+        while True:
+            # Write current segment
+            result.append(value[start_pos:end_pos])
</ins><span class="cx"> 
</span><ins>+            # Write escape
+            result.append(&quot;\\&quot;)
+            c = value[end_pos]
+            if c == '\r':
+                result.append(&quot;r&quot;)
+            elif c == '\n':
+                result.append(&quot;n&quot;)
+            elif c == ';':
+                result.append(&quot;;&quot;)
+            elif c == '\\':
+                result.append(&quot;\\&quot;)
+            elif c == ',':
+                result.append(&quot;,&quot;)
</ins><span class="cx"> 
</span><ins>+            # Bump past escapee and look for next segment
+            start_pos = end_pos + 1
</ins><span class="cx"> 
</span><del>-def writeTextValue(os, value):
-    try:
-        start_pos = 0
-        end_pos = find_first_of(value, &quot;\r\n;\\,&quot;, start_pos)
-        if end_pos != -1:
-            while True:
-                # Write current segment
-                os.write(value[start_pos:end_pos])
</del><ins>+            end_pos = find_first_of(value, &quot;\r\n;\\,&quot;, start_pos)
+            if end_pos == -1:
+                result.append(value[start_pos:])
+                break
+    else:
+        result.append(value)
</ins><span class="cx"> 
</span><del>-                # Write escape
-                os.write(&quot;\\&quot;)
-                c = value[end_pos]
-                if c == '\r':
-                    os.write(&quot;r&quot;)
-                elif c == '\n':
-                    os.write(&quot;n&quot;)
-                elif c == ';':
-                    os.write(&quot;;&quot;)
-                elif c == '\\':
-                    os.write(&quot;\\&quot;)
-                elif c == ',':
-                    os.write(&quot;,&quot;)
</del><ins>+    return &quot;&quot;.join(result)
</ins><span class="cx"> 
</span><del>-                # Bump past escapee and look for next segment
-                start_pos = end_pos + 1
</del><span class="cx"> 
</span><del>-                end_pos = find_first_of(value, &quot;\r\n;\\,&quot;, start_pos)
-                if end_pos == -1:
-                    os.write(value[start_pos:])
-                    break
-        else:
-            os.write(value)
</del><span class="cx"> 
</span><del>-    except:
-        pass
-
-
-
</del><span class="cx"> def decodeTextValue(value):
</span><span class="cx">     os = StringIO.StringIO()
</span><span class="cx"> 
</span><span class="lines">@@ -287,17 +279,15 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def generateTextList(os, data, sep=';'):
</del><ins>+def getTextList(data, sep=';'):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Each element of the list must be separately escaped
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    try:
-        if isinstance(data, basestring):
-            data = (data,)
-        results = [escapeTextValue(value) for value in data]
-        os.write(sep.join(results))
-    except:
-        pass
</del><ins>+    if isinstance(data, basestring):
+        data = (data,)
+    elif data is None:
+        data = (&quot;&quot;,)
+    return sep.join([getTextValue(value) for value in data])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -343,26 +333,10 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def generateDoubleNestedList(os, data):
-    try:
-        def _writeElement(item):
-            if isinstance(item, basestring):
-                writeTextValue(os, item)
-            else:
-                if item:
-                    writeTextValue(os, item[0])
-                    for bit in item[1:]:
-                        os.write(&quot;,&quot;)
-                        writeTextValue(os, bit)
</del><ins>+def getDoubleNestedList(data):
+    return &quot;;&quot;.join([getTextList(item, &quot;,&quot;) for item in data])
</ins><span class="cx"> 
</span><del>-        for item in data[:-1]:
-            _writeElement(item)
-            os.write(&quot;;&quot;)
-        _writeElement(data[-1])
</del><span class="cx"> 
</span><del>-    except:
-        pass
-
</del><span class="cx"> # Date/time calcs
</span><span class="cx"> days_in_month = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
</span><span class="cx"> days_in_month_leap = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarvaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/value.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/value.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/value.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -96,6 +96,10 @@
</span><span class="cx">         raise NotImplementedError
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getTextValue(self):
+        raise NotImplementedError
+
+
</ins><span class="cx">     def setValue(self, value):
</span><span class="cx">         raise NotImplementedError
</span><span class="cx"> 
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarvalueutilspy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/valueutils.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/valueutils.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/valueutils.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -106,5 +106,9 @@
</span><span class="cx">         return self.mValue
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getTextValue(self):
+        return self.mValue.getText()
+
+
</ins><span class="cx">     def setValue(self, value):
</span><span class="cx">         self.mValue = value
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarvcardadrpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/vcard/adr.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/vcard/adr.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/vcard/adr.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -116,9 +116,16 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def generate(self, os):
</span><del>-        utils.generateDoubleNestedList(os, self.mValue)
</del><ins>+        try:
+            os.write(self.getText())
+        except:
+            pass
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getText(self):
+        return utils.getDoubleNestedList(self.mValue)
+
+
</ins><span class="cx">     def parseJSON(self, jobject):
</span><span class="cx">         self.mValue = tuple(map(lambda x: x.encode(&quot;utf-8&quot;), jobject))
</span><span class="cx"> 
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarvcardnpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/vcard/n.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/vcard/n.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/vcard/n.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -101,7 +101,7 @@
</span><span class="cx"> 
</span><span class="cx">         results = []
</span><span class="cx">         for i in (N.PREFIX, N.FIRST, N.MIDDLE, N.LAST, N.SUFFIX):
</span><del>-            result = _stringOrList(self.mValue[i])
</del><ins>+            result = _stringOrList(self.mValue[i]) if self.mValue[i] else &quot;&quot;
</ins><span class="cx">             if result:
</span><span class="cx">                 results.append(result)
</span><span class="cx"> 
</span><span class="lines">@@ -113,9 +113,16 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def generate(self, os):
</span><del>-        utils.generateDoubleNestedList(os, self.mValue)
</del><ins>+        try:
+            os.write(self.getText())
+        except:
+            pass
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getText(self):
+        return utils.getDoubleNestedList(self.mValue)
+
+
</ins><span class="cx">     def parseJSON(self, jobject):
</span><span class="cx">         self.mValue = tuple(map(lambda x: x.encode(&quot;utf-8&quot;), jobject))
</span><span class="cx"> 
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarvcardorgvaluepy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/vcard/orgvalue.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/vcard/orgvalue.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/vcard/orgvalue.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -41,9 +41,16 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def generate(self, os):
</span><del>-        utils.generateTextList(os, self.mValue, ';')
</del><ins>+        try:
+            os.write(self.getTextValue())
+        except:
+            pass
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getTextValue(self):
+        return utils.getTextList(self.mValue, ';')
+
+
</ins><span class="cx">     def parseJSONValue(self, jobject):
</span><span class="cx">         self.mValue = tuple(map(lambda x: x.encode(&quot;utf-8&quot;), jobject))
</span><span class="cx"> 
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarvcardteststest_adrpy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/vcard/tests/test_adr.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/vcard/tests/test_adr.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/vcard/tests/test_adr.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -29,6 +29,10 @@
</span><span class="cx">                 ((&quot;pobox&quot;,), (&quot;extended&quot;,), (&quot;street1&quot;, &quot;street2&quot;,), &quot;locality&quot;, &quot;region&quot;, (), &quot;country&quot;),
</span><span class="cx">                 &quot;pobox;extended;street1,street2;locality;region;;country&quot;,
</span><span class="cx">             ),
</span><ins>+            (
+                (&quot;&quot;, None, (&quot;street1&quot;, &quot;street2&quot;,), &quot;locality&quot;, &quot;region&quot;, (), &quot;country&quot;),
+                &quot;;;street1,street2;locality;region;;country&quot;,
+            ),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         for args, result in data:
</span></span></pre></div>
<a id="PyCalendarbranchespatchsrcpycalendarvcardteststest_npy"></a>
<div class="modfile"><h4>Modified: PyCalendar/branches/patch/src/pycalendar/vcard/tests/test_n.py (15376 => 15377)</h4>
<pre class="diff"><span>
<span class="info">--- PyCalendar/branches/patch/src/pycalendar/vcard/tests/test_n.py        2015-12-10 21:47:23 UTC (rev 15376)
+++ PyCalendar/branches/patch/src/pycalendar/vcard/tests/test_n.py        2015-12-10 22:01:17 UTC (rev 15377)
</span><span class="lines">@@ -32,6 +32,11 @@
</span><span class="cx">                 &quot;last;first;middle1,middle2;;suffix&quot;,
</span><span class="cx">                 &quot;first middle1 middle2 last suffix&quot;,
</span><span class="cx">             ),
</span><ins>+            (
+                (&quot;last&quot;, (&quot;first&quot;,), &quot;&quot;, None, (&quot;suffix&quot;,)),
+                &quot;last;first;;;suffix&quot;,
+                &quot;first last suffix&quot;,
+            ),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         for args, result, fullName in data:
</span></span></pre>
</div>
</div>

</body>
</html>