DIRECTORY Ascii USING [Upper], BasicTime USING [Now, Unpack], Convert USING [Error, RopeFromTime, TimeFromRope], EditSpan USING [ChangeNesting, InsertTextNode, Place], IO USING [PutFR, PutFR1], Menus USING [AppendMenuEntry, ClickProc, CreateEntry, FindEntry, MenuEntry, ReplaceMenuEntry], Rope USING [Concat, Fetch, Find, Flatten, FromChar, Index, IsEmpty, IsPrefix, MaxLen, Replace, ROPE, Run, Size, SkipOver, SkipTo, Substr, Text], SystemNames USING [UserName], TEditInput USING [CommandProc, CurrentEvent, InterpretAtom, Register], TEditLocks USING [Lock, Unlock], TextEdit USING [ChangeLooks, FetchLooks, GetComment, GetFormat, Looks, noLooks, PutComment, PutFormat, ReplaceByRope], TextEditBogus USING [GetRope], TextNode USING [Level, Location, NodeItself, Ref, Span, StepForward], TiogaMenuOps USING [tiogaMenu], TiogaOps USING [Ref, ViewerDoc], UserProfile USING [Line]; TiogaComfortsImpl: CEDAR PROGRAM IMPORTS Ascii, BasicTime, Convert, EditSpan, IO, Menus, Rope, SystemNames, TEditInput, TEditLocks, TextEdit, TextEditBogus, TextNode, TiogaMenuOps, TiogaOps, UserProfile ~ BEGIN ROPE: TYPE ~ Rope.ROPE; Node: TYPE ~ TextNode.Ref; IsComment: PROC [node: Node] RETURNS [BOOL] ~ { RETURN [TextEdit.GetComment[node]] }; PrefixAns: TYPE ~ RECORD [prefixLength: INT, lineTerminable: BOOL, opener, closer: ROPE]; CommentPrefix: PROC [rope: ROPE, start: INT] RETURNS [PrefixAns] ~ { IF (rope.Run[start, "--"] = 2) THEN RETURN [[2, TRUE, NIL, "--"]] ELSE IF (rope.Run[start, "//"] = 2) THEN RETURN [[2, TRUE, NIL, NIL]] ELSE IF rope.Run[start, ";"] = 1 THEN RETURN [[rope.SkipOver[start, ";"] - start, TRUE, NIL, NIL]] ELSE IF rope.Run[start, "(*"] = 2 THEN RETURN [[2, FALSE, "(*", "*)"]] ELSE IF rope.Run[start, "/*"] = 2 THEN RETURN [[2, FALSE, "/*", "*/"]] ELSE RETURN [[0, TRUE, NIL, NIL]]; }; SkipOverBackward: PROC [s: ROPE, pos: INT ¬ Rope.MaxLen, skip: ROPE] RETURNS [INT] ~ { skipText: Rope.Text = skip.Flatten[]; skiplen: NAT ~ IF skipText = NIL THEN 0 ELSE skipText.Size[]; slen: INT ~ s.Size[]; start: INT ~ MIN[slen-1, pos]; IF start < 0 OR s.IsEmpty[] THEN RETURN [-1]; IF skiplen=0 THEN RETURN[start]; FOR n: INT ¬ start, n - 1 WHILE n>=0 DO c: CHAR ~ s.Fetch[n]; FOR i: NAT IN [0..skiplen) DO IF c = skipText[i] THEN EXIT; REPEAT FINISHED => RETURN [n]; ENDLOOP; ENDLOOP; RETURN [-1]; }; EnumerateInitialCommentLines: PROC [ root: Node, proc: PROC [text: ROPE, node: Node, start, length, after: INT, hasLineBreak: BOOL] RETURNS [continue: BOOL]] RETURNS [finalNode: Node ¬ NIL, finalStart, finalAfter: INT ¬ 0] ~ { index: INT ¬ 0; node: Node ¬ TextNode.StepForward[root]; contents: ROPE ¬ TextEditBogus.GetRope[node]; hasLineBreak: BOOL ¬ TRUE; DO GetTrimmedLine: PROC [begin: INT] RETURNS [start, length: INT, bad: BOOL ¬ FALSE] ~ { lim: INT ¬ contents.Size[]; lineBreak: INT ¬ contents.SkipTo[begin, "\l\r"]; fin: INT; --index of first char beyond comment contents IF pa.closer=NIL THEN { IF NOT pa.lineTerminable THEN ERROR; fin ¬ lineBreak; index ¬ fin + 1; -- set up for next line hasLineBreak ¬ lineBreak 0 AND lc < lim DO IF lo0 THEN { IF lo0 THEN after ¬ fin ¬ lim} ELSE { fin ¬ contents.Index[begin, pa.closer]; IF finafter AND SkipOverBackward[contents, index-1, " \t"]>=after; IF (hasLineBreak ¬ index < contents.Size[]) THEN index ¬ index+1; }; start ¬ contents.SkipOver[begin, " \t"]; fin ¬ SkipOverBackward[contents, fin - 1, " \t"]; length ¬ MAX[0, fin - start + 1]; RETURN}; start, length: INT; pa: PrefixAns; IF (pa ¬ CommentPrefix[contents, index]).prefixLength > 0 OR IsComment[node] THEN { savedIndex: INT ~ index; bad: BOOL; [start, length, bad] ¬ GetTrimmedLine[index + pa.prefixLength]; IF bad THEN EXIT; IF length > 0 OR pa.prefixLength > 0 THEN { -- don't count truly empty comments finalNode ¬ node; -- remember this one in case it's the last one finalStart ¬ savedIndex; finalAfter ¬ index; IF NOT proc[contents.Substr[start, length], node, start, length, index, hasLineBreak] THEN EXIT; }; } ELSE EXIT; IF index >= contents.Size[] THEN { -- on to the next node node ¬ TextNode.StepForward[node]; index ¬ 0; IF node = NIL THEN EXIT; contents ¬ TextEditBogus.GetRope[node]; }; ENDLOOP; }; CopyrightButton: Menus.ClickProc ~ { TEditInput.InterpretAtom[parent, $UpdateCopyrightNotice]; }; UpdateCopyrightNotice: TEditInput.CommandProc ~ { root: Node ~ TiogaOps.ViewerDoc[viewer]; hasNodes: BOOL ~ ( TextNode.StepForward[TextNode.StepForward[root]] # NIL ); Inner: PROC ~ { year: INT ~ BasicTime.Unpack[BasicTime.Now[]].year; done: BOOL ¬ FALSE; afterFirst: INT ¬ 0; firstHasLinebreak: BOOL ¬ FALSE; EachComment: PROC [text: ROPE, node: Node, start, length, after: INT, hasLineBreak: BOOL] RETURNS [continue: BOOL] ~ { index, foundYear, lastDigitPos: INT ¬ 0; thisLooks: TextEdit.Looks ~ IF hasNodes THEN eLooks ELSE TextEdit.noLooks; IF afterFirst=0 THEN {afterFirst ¬ after; firstHasLinebreak ¬ hasLineBreak}; IF NOT Rope.IsPrefix["Copyright ", text] THEN RETURN [TRUE]; index ¬ text.SkipTo[0, " \t"]; -- skip "Copyright" and the index ¬ text.SkipOver[index, " \t"]; index ¬ text.SkipTo[index, " \t"]; index ¬ text.SkipOver[index, " \t"]; lastDigitPos ¬ index-2; -- for malformed Copyright's, put year after symbol WHILE index < length DO c: CHAR ~ text.Fetch[index]; IF c IN ['0..'9] THEN { lastDigitPos ¬ index; index ¬ index + 1; IF foundYear < 1000 THEN foundYear ¬ foundYear * 10 + (c - '0) ELSE foundYear ¬ -1; } ELSE IF foundYear = year THEN EXIT ELSE IF c = ', OR c = ' OR c = '\t THEN { foundYear ¬ 0; index ¬ text.SkipOver[index + 1, ", \t"]; } ELSE { [] ¬ TextEdit.ReplaceByRope[root: root, dest: node, start: start + lastDigitPos + 1, len: 0, rope: IO.PutFR1[", %g", [integer[year]]], event: TEditInput.CurrentEvent[]]; EXIT; }; ENDLOOP; IF text.Run[9, " c "] = 3 THEN -- replace obsolete math font symbol [] ¬ TextEdit.ReplaceByRope[root: root, dest: node, rope: " Σ ", start: start + 9, len: 3, looks: thisLooks, event: TEditInput.CurrentEvent[]] ELSE IF text.Run[pos1: 9, s2: " (C) ", case: FALSE] = 5 THEN -- replace legally useless "(C)" symbol [] ¬ TextEdit.ReplaceByRope[root: root, dest: node, rope: " Σ ", start: start + 9, len: 5, looks: thisLooks, event: TEditInput.CurrentEvent[]] ELSE IF hasNodes THEN { looks: TextEdit.Looks ~ TextEdit.FetchLooks[node, start + 10]; IF NOT looks['e] THEN TextEdit.ChangeLooks[root: root, text: node, add: eLooks, start: start + 10, len: 1, event: TEditInput.CurrentEvent[]]; }; done ¬ TRUE; RETURN [FALSE]; }; [] ¬ EnumerateInitialCommentLines[root, EachComment]; IF NOT done THEN { -- We must add a new copyright line. holder: ROPE ~ UserProfile.Line["Tioga.CopyrightHolder", "Xerox Corporation"]; line: ROPE ~ IO.PutFR["Copyright Σ %g by %g. All rights reserved.", [integer[year]], [rope[holder]]]; node: Node ~ TextNode.StepForward[root]; -- The first real node of the document rope: ROPE ~ TextEditBogus.GetRope[node]; pa: PrefixAns ~ CommentPrefix[rope, 0]; prefix: ROPE ~ rope.Substr[len: pa.prefixLength]; index: INT ~ rope.SkipTo[skip: "\l\r"]; Add: PROC [node: Node, fullLine: ROPE, where: EditSpan.Place, comment: BOOL] ~ { child: Node ~ EditSpan.InsertTextNode[root: root, old: node, where: where, event: TEditInput.CurrentEvent[]]; [] ¬ TextEdit.ReplaceByRope[root: root, dest: child, rope: fullLine, event: TEditInput.CurrentEvent[]]; IF comment THEN TextEdit.PutComment[child, TRUE, TEditInput.CurrentEvent[]]; IF hasNodes THEN TextEdit.ChangeLooks[root: root, text: child, add: eLooks, start: 10, len: 1, event: TEditInput.CurrentEvent[]]; IF node # root AND where # $before THEN TextEdit.PutFormat[node: child, format: TextEdit.GetFormat[node], event: TEditInput.CurrentEvent[]]; }; IF NOT IsComment[node] AND pa.prefixLength = 0 THEN -- no initial comment Add[node, line, $before, TRUE] ELSE IF firstHasLinebreak THEN { -- first comment ends with a newline newline: CHAR ~ rope.Fetch[index]; fullLine: ROPE ~ MakeComment[line, prefix, pa].Concat[Rope.FromChar[newline]]; [] ¬ TextEdit.ReplaceByRope[root: root, dest: node, start: afterFirst, len: 0, rope: fullLine, event: TEditInput.CurrentEvent[]]; } ELSE IF pa.prefixLength = 0 THEN Add[node, line, $child, TRUE] ELSE Add[node, MakeComment[line, prefix, pa], $after, IsComment[node]]; }; }; [] ¬ TEditLocks.Lock[root, "UpdateCopyrightNotice"]; Inner[! UNWIND => TEditLocks.Unlock[root]]; TEditLocks.Unlock[root]; RETURN [recordAtom: TRUE, quit: TRUE]; }; MakeComment: PROC [content, prefix: ROPE, pa: PrefixAns] RETURNS [ROPE] ~ { IF pa.prefixLength=0 THEN RETURN [content] ELSE IF pa.lineTerminable THEN RETURN IO.PutFR["%g %g", [rope[prefix]], [rope[content]] ] ELSE RETURN IO.PutFR["%g %g %g", [rope[prefix]], [rope[content]], [rope[pa.closer]] ]}; UpdateLastEditedLine: TEditInput.CommandProc ~ { root: Node ~ TiogaOps.ViewerDoc[viewer]; userName: ROPE ~ GetNiceUserName[]; editedBy: ROPE ~ UserProfile.Line["Tioga.LastEdited", userName.Concat[","]]; Inner: PROC ~ { dateNode: Node ¬ NIL; dateStart, dateLength: INT ¬ 0; lastHasLineBreak: BOOL ¬ FALSE; EachComment: PROC [text: ROPE, node: Node, start, length, after: INT, hasLineBreak: BOOL] RETURNS [continue: BOOL] ~ { lastHasLineBreak ¬ hasLineBreak; IF text.Find[userName] >= 0 OR text.Find[editedBy] >= 0 THEN { dStart, dLength: INT; [dStart, dLength] ¬ FindDate[text]; IF dStart >= 0 THEN { -- We got one, Martha! dateNode ¬ node; dateStart ¬ dStart + start; -- offset in node as opposed to text dateLength ¬ dLength; }; }; RETURN [TRUE]; -- look for the last such node }; lastCommentNode: Node; lastCommentStart, lastCommentAfter: INT; nowRope: ROPE ~ Convert.RopeFromTime[BasicTime.Now[]]; [lastCommentNode, lastCommentStart, lastCommentAfter] ¬ EnumerateInitialCommentLines[root, EachComment]; IF dateNode # NIL THEN { -- Replace existing date [] ¬ TextEdit.ReplaceByRope[root: root, dest: dateNode, rope: nowRope, start: dateStart, len: dateLength, event: TEditInput.CurrentEvent[]]; } ELSE IF lastCommentNode = NIL THEN -- No comments in document, so don't write a line RETURN ELSE { -- We must make a new line rope: ROPE ~ TextEditBogus.GetRope[lastCommentNode]; eol: INT ~ rope.SkipTo[lastCommentStart, "\l\r"]; pa: PrefixAns ~ CommentPrefix[rope, lastCommentStart]; prefix: ROPE ~ rope.Substr[lastCommentStart, pa.prefixLength]; shortLine: ROPE ~ IO.PutFR["%g %g", [rope[editedBy]], [rope[nowRope]]]; prefixedLine: ROPE ~ MakeComment[shortLine, prefix, pa]; IF lastHasLineBreak THEN { -- line-break present so insert on new line fullLine: ROPE ~ prefixedLine.Concat[Rope.FromChar[rope.Fetch[eol]]]; [] ¬ TextEdit.ReplaceByRope[root: root, dest: lastCommentNode, start: lastCommentAfter, len: 0, rope: fullLine, event: TEditInput.CurrentEvent[]]; } ELSE { -- no line-break so make new line a new node newNode: Node ~ EditSpan.InsertTextNode[root: root, old: lastCommentNode, where: $after, inherit: TRUE, event: TEditInput.CurrentEvent[]]; newNodeLocation: TextNode.Location ~ [node: newNode, where: TextNode.NodeItself]; newNodeSpan: TextNode.Span ~ [start: newNodeLocation, end: newNodeLocation]; [] ¬ TextEdit.ReplaceByRope[root: root, dest: newNode, rope: prefixedLine, event: TEditInput.CurrentEvent[]]; IF pa.prefixLength = 0 THEN [] ¬ EditSpan.ChangeNesting[root: root, span: newNodeSpan, change: 2 - TextNode.Level[newNode], event: TEditInput.CurrentEvent[]]; }; }; }; [] ¬ TEditLocks.Lock[root, "UpdateLastEditedLine"]; Inner[! UNWIND => TEditLocks.Unlock[root]]; TEditLocks.Unlock[root]; RETURN [recordAtom: FALSE, quit: FALSE]; }; GetNiceUserName: PROC RETURNS [ROPE] = { user: ROPE ¬ SystemNames.UserName[]; IF Rope.IsEmpty[user] THEN RETURN [user]; user ¬ Rope.Replace[user, 0, 1, Rope.FromChar[Ascii.Upper[Rope.Fetch[user, 0]]]]; RETURN [user]; }; monthList: LIST OF ROPE = LIST [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; maxDateRope: NAT ~ 256; FindDate: PROC [rope: ROPE] RETURNS [start, length: INT] = { len: INT = rope.Size[]; IF len <= maxDateRope THEN FOR months: LIST OF ROPE ¬ monthList, months.rest UNTIL months = NIL DO pos, end: INT ¬ 0; WHILE pos < len DO start ¬ rope.Find[months.first, pos]; IF start < 0 THEN EXIT; -- not this month SELECT TRUE FROM (end ¬ rope.Find["ST", start]) # -1 => {end ¬ end + 1}; (end ¬ rope.Find["DT", start]) # -1 => {end ¬ end + 1}; (end ¬ rope.Find["GMT", start]) # -1 => {end ¬ end + 2}; (end ¬ rope.Find[" am", start, FALSE]) # -1 => {end ¬ end + 2}; (end ¬ rope.Find[" pm", start, FALSE]) # -1 => {end ¬ end + 2}; ENDCASE => EXIT; -- no plausible ending! [] ¬ Convert.TimeFromRope[rope.Substr[start, end - start + 1] ! Convert.Error--[reason: ErrorType, index: INT]-- => { pos ¬ start + 3; LOOP;}]; RETURN[start, end - start + 1]; ENDLOOP; ENDLOOP; RETURN[-1, -1]; }; InstallMenuButton: PROC [name: ROPE, proc: Menus.ClickProc] = { old: Menus.MenuEntry = Menus.FindEntry[menu: TiogaMenuOps.tiogaMenu, entryName: name]; new: Menus.MenuEntry = Menus.CreateEntry[name: name, proc: proc, fork: FALSE]; IF old = NIL THEN Menus.AppendMenuEntry[TiogaMenuOps.tiogaMenu, new] ELSE Menus.ReplaceMenuEntry[TiogaMenuOps.tiogaMenu, old, new]; }; eLooks: TextEdit.Looks ¬ TextEdit.noLooks; eLooks['e] ¬ TRUE; InstallMenuButton["Σ", CopyrightButton]; TEditInput.Register[$UpdateCopyrightNotice, UpdateCopyrightNotice]; TEditInput.Register[$RedSave, UpdateLastEditedLine]; TEditInput.Register[$RedStore, UpdateLastEditedLine]; END. Ζ TiogaComfortsImpl.mesa Copyright Σ 1990, 1991, 1992, 1993 by Xerox Corporation. All rights reserved. Last changed by Pavel on February 12, 1990 4:13 pm PST Willie-s, June 27, 1991 10:44 am PDT Michael Plass, September 24, 1991 2:22 pm PDT Doug Wyatt, February 28, 1992 10:20 am PST Last tweaked by Mike Spreitzer September 2, 1993 2:56 pm PDT Like Rope.SkipOver, but backwards. Returns the highest position N in s such that s[N] is NOT in the skip string and N <= pos. If no such character occurs in s, then return -1. Calls proc on the text and location of each line of comments at the beginning of the given document until proc returns FALSE. Returns the location of the last comment line found. In all cases, the ``line'' is not considered to include any opening or closing comment characters (e.g., "--" or ";"), leading or trailing whitespace, or the terminating newline character if any. Disregard comments followed by non-white chars. [viewer: ViewerClasses.Viewer] RETURNS [recordAtom: BOOL ¬ TRUE, quit: BOOL ¬ FALSE] The copyright line is assumed to have the format Copyright , , ..., We fix up the to be a true copyright symbol and add the current year to the end of the list if it's not already present. MessageWindow.Append[text.Concat["||"]]; MessageWindow.Clear[]; Add the line after the first comment, copying the comment characters and EOL character of the first line. [viewer: ViewerClasses.Viewer] RETURNS [recordAtom: BOOL ¬ TRUE, quit: BOOL ¬ FALSE] MessageWindow.Append[text.Concat["||"]]; MessageWindow.Clear[]; MessageWindow.Append[IO.PutFR["s: %g, l: %g", [integer[dateStart]], [integer[dateLength]]]]; In FindDate, lines that are longer than this length are assumed to NOT have embedded dates. This avoids searches of ludicrous length. Returns location in rope where a valid date occurs; [-1, -1] indicates no valid date. month may conflict with someone's name, so skip this and try further on Κ%–(cedarcode) style•NewlineDelimiter ™codešœ™Kšœ ΟeœC™NK™6K™$K™-K™*K™<—K˜šΟk ˜ Kšœžœ ˜Kšœ žœ˜Kšœžœ%˜2Kšœ žœ(˜6Kšžœžœ˜KšœžœS˜^KšœžœUžœ-˜Kšœ žœ ˜Kšœ žœ6˜FKšœ žœ˜ Kšœ žœh˜vKšœžœ ˜Kšœ žœ7˜EKšœ žœ ˜Kšœ žœ˜ Kšœ žœ˜—K˜K˜KšΟnœžœž˜ Kšžœ&žœz˜©šœž˜K˜Kšžœžœžœ˜Kšœžœ˜K˜šŸ œžœžœžœ˜/Kšžœ˜"K˜—K˜šœ ž˜Kš œžœžœžœžœ˜I—K˜š Ÿ œžœžœ žœžœ˜DKš žœžœžœžœžœ˜BKšžœžœžœžœžœžœžœ˜Ešžœžœž˜%Kšžœ&žœžœžœ˜<—Kš žœžœžœžœžœ˜FKš žœžœžœžœžœ˜FKš žœžœžœžœžœ˜"K˜—K˜šŸœžœžœžœžœžœžœ˜VKšœ±™±Kšœ%˜%Kš œ žœžœ žœžœžœ˜=Kšœžœ ˜Kšœžœžœ˜Kšžœ žœ žœžœ˜-Kšžœ žœžœ˜ šžœžœžœž˜'Kšœžœ˜šžœžœžœž˜Kšžœžœžœ˜K˜šž˜Kšžœžœ˜—Kšžœ˜—Kšžœ˜—Kšžœ˜ K˜—K˜šŸœžœžœžœ$žœžœžœ žœ˜Kšžœžœžœ ˜DKšœΟrœ` œ Πrsœό™ψKšœžœ˜K˜(Kšœ žœ˜-Kšœžœžœ˜šž˜šŸœžœ žœžœžœžœžœ˜UKšœžœ˜Kšœ žœ"˜0KšœžœΟc-˜7šžœ žœžœ˜Kšžœžœžœžœ˜$K˜Kšœ’˜(K˜—šžœ˜Kšœžœ’;˜GKšžœžœ˜*šžœ žœžœ˜Kšœžœ˜Kšœžœ$˜+Kšœžœ$˜+K˜ šžœ žœ ž˜šžœžœ˜!K˜Kšžœ žœ'˜7K˜&—šžœžœžœ˜&K˜&šžœ žœ˜Kšžœ žœ'˜7K˜'——Kšžœžœ˜ —Kšžœ˜Kšžœ žœ˜"—šžœ˜K˜'Kšžœ žœ žœ˜FK˜—K˜'šœžœ3˜HK™/—Kšžœ*žœ˜AK˜—K˜(K˜1Kšœ žœ˜!Kšžœ˜—Kšœžœ˜K˜K˜šžœ8žœžœ˜SKšœ žœ ˜Kšœžœ˜ K˜?Kšžœžœžœ˜šžœ žœžœ’#˜OKšœ’.˜@K˜K˜KšžœžœPžœžœ˜`K˜—Kšœ˜—šž˜Kšžœ˜—K˜šžœžœ’˜9K˜"K˜ Kšžœžœžœžœ˜Kšœ'˜'Kšœ˜—Kšžœ˜—K˜—K˜šŸœ˜$K˜9K˜—K˜šŸœ˜1Kš œžœžœžœžœžœ™TKšœ(˜(Kšœ žœ8žœ˜LšŸœžœ˜Kšœžœ*˜3Kšœžœžœ˜Kšœ žœ˜Kšœžœžœ˜ šŸ œžœžœ$žœžœžœ žœ˜v™0K™@—K™Kšœ žœ˜(Kšœžœ žœžœ˜JKšžœžœ7œ˜LK˜Kšœ(™(šžœžœ#žœ˜.Kšžœžœ˜—K˜Kšœ’$˜CKšœ$˜$Kšœ"˜"Kšœ$˜$Kšœ’3˜Kšžœž˜KšœžœΟt˜šžœ£œžœ žœ˜Kšœ˜K˜šžœž˜Kšœ&˜&—šž˜Kšœ˜—K˜—šžœžœžœ˜Kšž˜—š žœžœžœžœ žœ˜)Kšœ˜Kšœ)˜)Kšœ˜—šžœ˜KšœežœE˜¬Kšžœ˜K˜—Kšžœ˜—K˜šžœžœ’$˜CKšœŽ˜Ž—š žœžœ&žœžœ’'˜gKšœŽ˜Žšžœžœ žœ˜Kšœ>˜>Kšžœžœ žœx˜K˜——K˜Kšœžœ˜ Kšžœžœ˜K˜—K˜K™Kšœ5˜5K˜šžœžœžœ’$˜7KšœžœB˜NKšœžœžœW˜fKšœ)’&˜OKšœžœ˜)K˜'Kšœžœ%˜1Kšœžœ˜'šŸœžœžœ"žœ˜PKšœm˜mK˜Kšœg˜gšžœž˜Kšœžœ˜<—Kšžœ žœr˜‚šžœ žœžœ˜(Kšœe˜e—K˜—K˜š žœžœžœžœ’˜IKšœžœ˜—šžœžœžœ’$˜EK™iKšœ žœ˜"Kšœ žœ@˜N˜NK˜2—Kšœ˜—šžœžœž˜ Kšœžœ˜—šž˜K˜B—Kšœ˜—K˜—K˜K˜4Kšœžœ˜+Kšœ˜K˜Kšžœžœžœ˜&K˜—K˜š Ÿ œžœžœžœžœ˜KKšžœžœžœ ˜*Kš žœžœžœžœžœ1˜YKšžœžœžœI˜W—K˜šŸœ˜0Kš œžœžœžœžœžœ™TKšœ(˜(Kšœ žœ˜#Kšœ žœ>˜LšŸœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœžœ˜šŸ œžœžœ$žœžœžœ žœ˜vKšœ(™(K˜ šžœžœžœ˜>Kšœžœ˜Kšœ#˜#šžœ žœ’˜,Kšœ˜Kšœ’ Πcr’€˜@Kšœ˜K˜—Kšœ˜—Kšžœžœ’˜-K˜—K˜Kšœ$žœ˜(Kšœ žœ)˜6K˜K™K˜hšžœ žœžœ’˜1Kšœžœ £ œ £ œ™\Kš£>œ£,œ£˜ŒK˜—š žœžœžœžœ’1˜TKšž˜—šžœ’˜!Kšœžœ*˜4Kšœžœ)˜1K˜6Kšœžœ2˜>Kšœ žœžœ3˜GKšœžœ&˜8K˜šžœžœ’+˜FKšœ žœ7˜EKš£.œ£œœ£œ*£˜’K˜—šžœ’,˜3Kš£9œ£ž£ œ£˜ŠKšœQ˜QKšœL˜LKšœ£œ£œ6£˜mšžœž˜Kšœ€£˜‚—K˜—Kšœ˜—K˜—K˜Kšœ3˜3Kšœžœ˜+Kšœ˜K˜Kšžœžœžœ˜(K˜—K˜šŸœžœžœžœ˜(Kšœžœ˜$Kšžœžœžœ˜)KšœQ˜QKšžœ˜K˜—K˜š œ žœžœžœžœ˜ KšœT˜T—šœ žœ˜Kšœ†™†—K˜š Ÿœžœžœžœžœ˜™VKšœžœ˜šžœž˜š žœ žœžœžœžœ žœž˜GKšœ žœ˜šžœ ž˜Kšœ%˜%Kšžœ žœžœ’˜*šžœžœž˜Kšœ7˜7Kšœ7˜7Kšœ9˜9Kšœžœ˜?Kšœžœ˜?Kšžœžœ’˜)—šœ=˜=šœ’#œ˜7KšœG™GKšœ˜Kšžœ˜ ——Kšžœ˜Kšžœ˜—Kšžœ˜——Kšžœ ˜Kšœ˜—K˜šŸœžœžœ˜?šœ˜Kšœ?˜?—šœ˜Kšœ0žœ˜7—šžœž˜ Kšžœ3˜7Kšžœ:˜>—K˜—K˜Kšœ*˜*Kšœ žœ˜K˜Kšœ(˜(KšœC˜CK˜4K˜5—K˜Kšžœ˜—…—7 Nυ