DIRECTORY PFS, FileNames, RefText, Rope, SystemNames; FileNamesOnPFSImpl: CEDAR MONITOR IMPORTS PFS, RefText, Rope, SystemNames EXPORTS FileNames = BEGIN LOR: TYPE = LIST OF ROPE; LORA: TYPE = LIST OF REF ANY; ROPE: TYPE = Rope.ROPE; lastPreSlash1: ROPE ¬ NIL; lastPostSlash1: ROPE ¬ NIL; lastPreSlash2: ROPE ¬ NIL; lastPostSlash2: ROPE ¬ NIL; lastUserName: ROPE ¬ NIL; lastHomeDirectory: ROPE ¬ NIL; FileWithSearchRules: PUBLIC PROC [root: ROPE, defaultExtension: ROPE, requireExtension: BOOL ¬ TRUE, requireExact: BOOL ¬ TRUE, searchRules: REF ANY] RETURNS [fullPath: ROPE ¬ NIL, ambiguous: BOOL ¬ FALSE] = { fullPathName: BOOL ¬ FALSE; withExt: ROPE ¬ NIL; withStarExt: ROPE ¬ NIL; rules: LORA; ropeRules: LOR; list: LORA; ropeList: LOR; hasExtension: BOOL ¬ FALSE; hasVersion: BOOL ¬ FALSE; doRoot, doWithExt, doWithStarExt: BOOL ¬ FALSE; defaultExtLen: INT = Rope.Length[defaultExtension]; rootLen: INT ¬ Rope.Length[root ¬ ResolveRelativePath[root]]; Try: PROC [name: ROPE, wDir: ROPE ¬ NIL] RETURNS [found: BOOL] = { IF NOT Rope.IsEmpty[name] THEN { fullPathName: PFS.PATH ¬ NIL; Inner: PROC ~ { fullPathName ¬ PFS.FileInfo[name: PFS.PathFromRope[name] ! PFS.Error => IF error.group = user THEN CONTINUE].fullFName; }; IF wDir = NIL THEN Inner[] ELSE PFS.DoInWDir[PFS.PathFromRope[wDir], Inner]; fullPath ¬ PFS.RopeFromPath[fullPathName]; RETURN [fullPathName # NIL]; }; RETURN [FALSE]; }; UniqueMatch: PROC [name: ROPE, wDir: ROPE ¬ NIL] RETURNS [found: BOOL] = { lst: LOR ¬ NIL; p: PFS.NameProc = { continue ¬ lst = NIL; -- give up after this name if it is the second one IF lst # NIL THEN ambiguous ¬ TRUE; lst ¬ CONS[PFS.RopeFromPath[name], lst]; }; IF name.IsEmpty[] THEN { fullPath ¬ NIL; RETURN [TRUE]; }; BEGIN Inner: PROC ~ { PFS.EnumerateForNames[pattern: PFS.PathFromRope[name], proc: p ! PFS.Error => IF error.group = user THEN CONTINUE]; }; IF wDir = NIL THEN Inner[] ELSE PFS.DoInWDir[PFS.PathFromRope[wDir], Inner]; END; IF lst = NIL THEN RETURN [FALSE]; IF ambiguous THEN { fullPath ¬ NIL; RETURN [TRUE]; }; ambiguous ¬ TRUE; fullPath ¬ ConvertToSlashFormat[lst.first]; RETURN [TRUE]; }; IF rootLen = 0 THEN RETURN [NIL]; SELECT Rope.Fetch[root, 0] FROM '/, '[ => fullPathName ¬ TRUE; ENDCASE; FOR i: INT DECREASING IN [0..rootLen) DO SELECT Rope.Fetch[root, i] FROM '>, '], '/ => EXIT; '. => {hasExtension ¬ TRUE; EXIT}; '! => hasVersion ¬ TRUE; ENDCASE; ENDLOOP; IF requireExtension AND NOT hasExtension AND hasVersion THEN RETURN [NIL]; IF requireExtension AND hasExtension AND defaultExtLen # 0 THEN IF Rope.Find[s1: root, s2: defaultExtension, pos1: 0, case: FALSE] # (Rope.Index[s1: root, s2: "!", pos1: 0, case: TRUE] - defaultExtLen) THEN RETURN [NIL]; IF NOT hasExtension THEN { withExt ¬ Rope.Concat[root, defaultExtension]; withStarExt ¬ Rope.Cat[root, "*", defaultExtension]; IF NOT hasVersion THEN { withExt ¬ Rope.Concat[withExt, "!H"]; withStarExt ¬ Rope.Concat[withStarExt, "!H"]; }; }; doRoot ¬ NOT requireExtension OR hasExtension; doWithExt ¬ NOT hasExtension; doWithStarExt ¬ (NOT requireExact) AND (NOT hasExtension); IF fullPathName THEN { IF doRoot AND Try[name: root] THEN RETURN; IF doWithExt AND Try[name: withExt] THEN RETURN; IF doWithStarExt AND UniqueMatch[name: withStarExt] THEN RETURN; } ELSE { IF doRoot AND Try[name: root] THEN RETURN; WITH searchRules SELECT FROM lra: LORA => rules ¬ lra; lr: LOR => ropeRules ¬ lr; ENDCASE; list ¬ rules; ropeList ¬ ropeRules; IF doRoot THEN { WHILE list # NIL DO IF Try[name: root, wDir: NARROW[list.first, ROPE]] THEN RETURN; list ¬ list.rest; ENDLOOP; WHILE ropeList # NIL DO IF Try[name: root, wDir: ropeList.first] THEN RETURN; ropeList ¬ ropeList.rest; ENDLOOP; }; IF doWithExt AND Try[name: withExt] THEN RETURN; IF doWithExt THEN { list ¬ rules; ropeList ¬ ropeRules; WHILE list # NIL DO IF Try[name: withExt, wDir: NARROW[list.first, ROPE]] THEN RETURN; list ¬ list.rest; ENDLOOP; WHILE ropeList # NIL DO IF Try[name: withExt, wDir: ropeList.first] THEN RETURN; ropeList ¬ ropeList.rest; ENDLOOP; }; IF doWithStarExt THEN { IF UniqueMatch[name: withStarExt] THEN RETURN; list ¬ rules; ropeList ¬ ropeRules; WHILE list # NIL DO IF UniqueMatch[name: withStarExt, wDir: NARROW[list.first, ROPE]] THEN RETURN; list ¬ list.rest; ENDLOOP; WHILE ropeList # NIL DO IF UniqueMatch[name: withStarExt, wDir: ropeList.first] THEN RETURN; ropeList ¬ ropeList.rest; ENDLOOP; }; }; }; ParseState: TYPE = {idle, slash, oneDot, twoDots}; CurrentWorkingDirectory: PUBLIC PROC RETURNS [ROPE] = { RETURN [PFS.RopeFromPath[PFS.GetWDir[]]] }; ResolveRelativePath: PUBLIC PROC [path: ROPE] RETURNS [ROPE] = { i: INT ¬ 0; state: ParseState ¬ slash; pathLength: INT ¬ Rope.Length[path ¬ ConvertToSlashFormat[path]]; out: REF TEXT ¬ NIL; char: CHAR; usedCWD: BOOL ¬ FALSE; HandleDot: PROC [pos: INT] = { IF out = NIL THEN out ¬ RefText.New[pathLength]; IF pos - 1 > 0 THEN out ¬ RefText.AppendRope[to: out, from: path, start: 0, len: pos - 1]; IF pos >= pathLength THEN path ¬ NIL ELSE path ¬ path.Substr[pos + 1]; pathLength ¬ path.Length[]; -- recompute pathLength }; HandleDotDot: PROC [pos: INT] = { first: BOOL ¬ TRUE; IF out = NIL THEN out ¬ RefText.New[pathLength]; IF pos - 2 > 0 THEN out ¬ RefText.AppendRope[to: out, from: path, start: 0, len: pos - 2]; IF pos >= pathLength THEN path ¬ NIL ELSE path ¬ path.Substr[pos + 1]; pathLength ¬ path.Length[]; -- recompute pathLength pos ¬ out.length - 1; -- index of last character in out DO IF pos < 0 THEN { out.length ¬ 0; IF usedCWD THEN EXIT; usedCWD ¬ TRUE; out ¬ RefText.AppendRope[to: out, from: PFS.RopeFromPath[PFS.GetWDir[]]]; pos ¬ out.length - 1; -- continue scanning at end of CWD LOOP; }; IF out[pos] = '/ AND NOT first THEN { out.length ¬ pos + 1; EXIT; }; pos ¬ pos - 1; first ¬ FALSE; ENDLOOP; }; IF pathLength = 0 THEN RETURN [path]; IF path.Fetch[0] = '~ THEN { usedCWD ¬ TRUE; out ¬ RefText.AppendRope[to: RefText.New[pathLength], from: HomeDirectory[]]; out.length ¬ out.length - 1; state ¬ idle; pathLength ¬ Rope.Length[path ¬ Rope.Substr[path, 1]]; }; DO IF i >= pathLength THEN EXIT; char ¬ path.Fetch[i]; SELECT state FROM idle => IF char = '/ THEN state ¬ slash; slash => { SELECT char FROM '. => state ¬ oneDot; '/ => state ¬ slash; ENDCASE => state ¬ idle; }; oneDot => { SELECT char FROM '. => state ¬ twoDots; '/ => { HandleDot[i]; i ¬ 0; state ¬ slash; LOOP; }; ENDCASE => state ¬ idle; }; twoDots => { IF char = '/ THEN { HandleDotDot[i]; i ¬ 0; state ¬ slash; LOOP; } ELSE state ¬ idle; }; ENDCASE => ERROR; i ¬ i + 1; ENDLOOP; IF state = oneDot OR state = twoDots THEN { IF state = oneDot THEN HandleDot[pathLength] ELSE HandleDotDot[pathLength]; path ¬ Rope.FromRefText[out]; } ELSE IF out # NIL THEN path ¬ Rope.Concat[Rope.FromRefText[out], path]; IF path.IsEmpty[] AND NOT usedCWD THEN path ¬ PFS.RopeFromPath[PFS.GetWDir[]]; RETURN [path]; }; ConvertToSlashFormat: PUBLIC ENTRY PROC [path: ROPE] RETURNS [ROPE] = { ENABLE UNWIND => NULL; SELECT TRUE FROM Rope.Equal[path, lastPreSlash1] => RETURN [lastPostSlash1]; Rope.Equal[path, lastPreSlash2] => RETURN [lastPostSlash2]; ENDCASE => { ToSlash: Rope.TranslatorType = { gnu: CHAR ¬ SELECT old FROM '[, '], '<, '> => '/ ENDCASE => old; RETURN[gnu]; }; pos: INT ¬ path.Find["]<"]; lastPreSlash1 ¬ lastPreSlash2; lastPostSlash1 ¬ lastPostSlash2; lastPreSlash2 ¬ path; IF pos # -1 THEN path ¬ Rope.Replace[base: path, start: pos, len: 2, with: "/"]; IF Rope.SkipTo[s: path, pos: 0, skip: "[]<>"] # path.Length[] THEN path ¬ Rope.Translate[base: path, translator: ToSlash]; lastPostSlash2 ¬ path; RETURN [path]; }; }; HomeDirectory: PUBLIC ENTRY PROC RETURNS [ROPE] = { ENABLE UNWIND => NULL; user: ROPE ¬ SystemNames.UserName[]; --UserCredentials.Get[].name; IF ( NOT Rope.Equal[lastUserName, user] ) THEN { lastHomeDirectory ¬ SystemNames.SimpleHomeDirectory[]; lastUserName ¬ user; }; RETURN [lastHomeDirectory]; }; IsADirectory: PUBLIC PROC [path: ROPE] RETURNS [BOOL] = { length: INT ¬ Rope.Length[path]; IF length # 0 THEN SELECT Rope.Fetch[path, length-1] FROM '/, '], '> => RETURN [TRUE]; ENDCASE; RETURN [FALSE]; }; IsAPattern: PUBLIC PROC [path: ROPE] RETURNS [BOOL] = { RETURN [Rope.SkipTo[path, 0, "*"] # Rope.Length[path]]; }; GetShortName: PUBLIC PROC [path: ROPE, stripOffVersionNumber: BOOL ¬ TRUE] RETURNS [ROPE] = { len: INT ¬ Rope.Length[path]; bang: INT ¬ len; pos: INT ¬ len; WHILE pos # 0 DO np: INT ¬ pos - 1; c: CHAR ¬ Rope.Fetch[path, np]; SELECT c FROM '! => IF stripOffVersionNumber THEN bang ¬ np; '>, '], '/ => RETURN [Rope.Substr[path, pos, bang-pos]]; ENDCASE; pos ¬ np; ENDLOOP; RETURN [Rope.Substr[path, 0, bang]]; }; Tail: PUBLIC PROC [s: ROPE, char: CHAR] RETURNS [ROPE] = { pos: INT ¬ s.Length[]; WHILE pos # 0 DO IF Rope.Fetch[s, pos-1] = char THEN RETURN [Rope.Substr[s, pos]]; pos ¬ pos - 1; ENDLOOP; RETURN [s]; }; StripVersionNumber: PUBLIC PROC [path: ROPE] RETURNS [ROPE] = { pos: INT ¬ path.Find["!"]; IF pos = -1 THEN RETURN [path]; RETURN [path.Substr[0, pos]]; }; IsRemote: PUBLIC PROC [path: ROPE] RETURNS [BOOL] = { first, second: CHAR; IF path.Length[] < 2 THEN RETURN [FALSE]; first ¬ path.Fetch[0]; second ¬ path.Fetch[1]; SELECT first FROM '/ => RETURN [second # '/]; '[ => RETURN [second # ']]; ENDCASE; RETURN [FALSE]; }; InASubdirectory: PUBLIC PROC [parent: ROPE, path: ROPE] RETURNS [BOOL] = { pos: INT; path ¬ ConvertToSlashFormat[path]; parent ¬ ConvertToSlashFormat[parent]; pos ¬ Rope.Find[s1: path, s2: parent, case: FALSE]; IF pos = -1 THEN RETURN [FALSE]; RETURN [Rope.Find[s1: path, s2: "/", pos1: parent.Length[]] # -1]; }; FirstSubdirectory: PUBLIC PROC [parent: ROPE, path: ROPE] RETURNS [ROPE] = { pos: INT; end: INT; path ¬ ConvertToSlashFormat[path]; parent ¬ ConvertToSlashFormat[parent]; pos ¬ Rope.Find[s1: path, s2: parent, case: FALSE]; IF pos # 0 THEN RETURN [NIL]; pos ¬ parent.Length[]; IF pos = 0 THEN RETURN [NIL]; end ¬ Rope.Find[s1: path, s2: "/", pos1: pos]; IF end = -1 THEN RETURN [NIL]; RETURN [Rope.Substr[base: path, start: 0, len: end + 1]]; }; DirectoryContaining: PUBLIC PROC [path: ROPE, pos: INT] RETURNS [ROPE] = { WHILE pos > 0 DO c: CHAR ¬ Rope.Fetch[path, pos ¬ pos - 1]; SELECT c FROM '>, '], '/ => RETURN [Rope.Substr[path, 0, pos+1]]; ENDCASE; ENDLOOP; RETURN [NIL]; }; Directory: PUBLIC PROC [path: ROPE] RETURNS [ROPE] = { RETURN [DirectoryContaining[path, Rope.Length[path]]]; }; Parent: PUBLIC PROC [path: ROPE] RETURNS [ROPE] = { len: INT ¬ Rope.Length[path]; pos: INT ¬ len; first: BOOL ¬ TRUE; WHILE pos # 0 DO c: CHAR ¬ Rope.Fetch[path, pos - 1]; SELECT c FROM '>, '], '/ => IF pos # len THEN RETURN [Rope.Substr[path, 0, pos]]; ENDCASE; pos ¬ pos - 1; ENDLOOP; RETURN [NIL]; }; RelativizePath: PUBLIC PROC [path: ROPE] RETURNS [ROPE] = { homeDir: ROPE ¬ HomeDirectory[]; homeDirLen: INT ¬ Rope.Length[homeDir]; slashName: ROPE ¬ ConvertToSlashFormat[path]; IF Rope.Run[homeDir, 0, slashName, 0, FALSE] = homeDirLen THEN path ¬ Rope.Replace[path, 0, homeDirLen, "~/"]; RETURN [path]; }; END.  FileNamesOnPFSImpl.mesa Copyright Σ 1985, 1987, 1989, 1991 by Xerox Corporation. All rights reserved. L. Stewart, January 16, 1984 1:03 pm Russ Atkinson (RRA) February 16, 1987 7:46:03 pm PST Doug Wyatt, January 24, 1987 11:50:39 pm PST JKF December 22, 1988 2:35:42 pm PST Bill Jackson (bj) October 24, 1988 12:42:06 pm PDT Carl Hauser, November 30, 1988 10:45:59 am PST Pier, February 2, 1989 2:20:16 pm PST Michael Plass, November 29, 1989 1:00:07 pm PST Clients of this should be converted to use PFS directly. Willie-s, August 20, 1991 5:46 pm PDT A few small caches FileWithSearchRules uses the working directory and the search rules to try to translate the short name of a file into a full path name. Calls ResolveRelativePath first. NIL is returned if the file cannot be found. searchRules should either be a LIST OF REF ANY or a LIST OF Rope.ROPE. If requireExtension, then if root has an extension, it must be the same as defaultExtension. FileWithSearchRules adds !H to all pattern matches IF requireExact = TRUE, then FileWithSearchRules will not try for a unique pattern match, but will only check exact names. FileWithSearchRules returns NIL and ambiguous = TRUE if it finds more than one matching file. FileNames returns not-NIL and ambiguous = TRUE if the result was found as a consequence of an exact pattern match. FileWithSearchRules uses the following rules: IF root is a full path name THEN { tries root tries Concat[root, defaultExtension] tries for a unique match for Cat[root, *, defaultExtension] } ELSE { tries root (automatically used $WorkingDirectory) FOR wdir _ each element of search rules { tries Concat[wdir, root] } tries Concat[root, defaultExtension] (automatically used $WorkingDirectory) FOR wdir _ each element of search rules { tries Cat[wdir, root, defaultExtension] } IF NOT requireExact THEN FOR wdir _ each element of search rules { tries for a unique match for Cat[wdir, root, *, defaultExtension] } } Returns NIL if can't find it. Exactly one match try the exact matches try for a unique match If path starts with or contains ./ or ../, ResolveRelativePath converts it into the equivalent full path name using the $WorkingDirectory property on the process properties list. If path is exactly . or .., ResolveRelativePath converts it to the current or parent directory. HandleDot is called with pos pointing to the location of the trailing '/ is the "/./" construct, it works if either the leading or trailing slash aren't there, as in "./xxx" or "xxx/." After execution of HandleDot, out holds the path tp to and including the leading / while path holds the path after the trailing slash. If the leading slash doesn't exist, then out will be allocated but empty. If the trailing slash doesn't exist (or was the last character in path), then path will be empty out now includes up to and including the leading slash. path now starts at the character after the trailing slash HandleDotDot is called with pos pointing to the location of the trailing '/ is the "/../" construct. It worksif either the leading or trailing slash aren't there, as in "../xxx" or "xxx/.." After execution of HandleDotDot, out holds the path tp to and including the / terminating the parent directory while path holds the path after the trailing slash. If the leading slash doesn't exist, then out will contain the parent of CWD. If the trailing slash doesn't exist (or was the last character in path), then path will be empty pos is the location of the trailing '/ out now contains the path up to and including the leading slash path now starts at the character after the trailing slash Now strip back out to the last slash, provided it is not the first character examined Put in ///Users/name/, then pull back on the trailing / delete the "./" construct continue scanning from beginning (since HandleDot has re-written path) handle the "../" construct continue scanning from beginning (since HandleDot has re-written path) The next character we get should be the one after the slash handle state at end of path The next lines call HandleDot (or DotDot) with a pointer to the '/ which is off the end of the path. TYPE = PROC [old: CHAR] RETURNS [CHAR] Convert to slashes Tail returns the part of a rope after the last instance of char. Return the prefix of the part of name before pos which is the name of a directory. Return the directory part of a name. If path is a directory, then returns the parent directory. If path is a file, then returns the current directory. In either case, this means that Parent returns the directory which contains the current object. Extras Κ“–(cedarcode) style•NewlineDelimiter ™codešœ™Kšœ ΟeœC™NK™$KšœΟkœž™4Kšœ)ž™,Kšžœž™$Kšœ/ž™2Kšœ+ž™.Kšœ%™%K™/K™K™8K™%K™—šž ˜ Kšžœ˜Kšœ ˜ Kšœ˜Kšœ˜K˜ —K˜šΟnœžœž˜!Kšžœžœ˜(Kšžœ ˜Kšœž˜K˜Kš žœžœžœžœžœ˜Kš žœžœžœžœžœžœ˜Kšžœžœžœ˜K˜—™Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœžœ˜K˜—š Ÿœžœžœžœžœžœžœžœžœžœžœžœ žœžœ žœžœ˜ΡKšœ«žœ)™ΧKšœžœžœžœžœžœžœžœ™FKšœ\™\K™2Kšžœžœd™zKšœžœžœ)™]KšœžœžœD™ršœ-™-šžœ™šžœ™Kšœ ™ Kšœ$™$Kšœ;™;K™—šžœ™Kšœ1™1šžœ&™)Kšœ™K™—KšœK™Kšžœ&™)Kšœ'™'K™—šžœžœžœžœ&™BKšœA™AK™—K™——Kšœžœ™—Kšœžœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœžœ˜ Kšœ žœ˜Kšœžœ˜ Kšœ žœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœ"žœžœ˜/Kšœžœ!˜3Kšœ žœ1˜=šŸœžœžœžœžœžœ žœ˜Bšžœžœžœ˜ Kšœžœžœžœ˜šŸœžœ˜šœžœžœ˜8Kš œžœ žœžœžœ ˜>—Kšœ˜—Kš žœžœžœ žœžœ žœ˜LKšœ žœ˜*Kšžœžœ˜K˜—Kšžœžœ˜K˜—šŸ œžœžœžœžœžœ žœ˜JKšœžœžœ˜šœžœ ˜KšœžœΟc2˜IKšžœžœžœ žœ˜#Kšœžœžœ˜(Kšœ˜—šžœžœ˜Kšœ žœ˜Kšžœžœ˜K˜—šž˜šŸœžœ˜šžœžœ˜>Kš œžœ žœžœžœ˜4—Kšœ˜—Kš žœžœžœ žœžœ žœ˜LKšžœ˜—Kš žœžœžœžœžœ˜!šžœ žœ˜Kšœ žœ˜Kšžœžœ˜K˜—K™Kšœ žœ˜Kšœ+˜+Kšžœžœ˜K˜—K˜Kšžœ žœžœžœ˜!šžœž˜Kšœžœ˜Kšžœ˜—š žœžœž œžœž˜(šžœž˜Kšœžœ˜Kšœžœžœ˜"Kšœžœ˜Kšžœ˜—Kšžœ˜—Kšžœžœžœžœ žœžœžœ˜Jšžœžœžœž˜?šžœ:žœ2žœž˜ŽKšžœžœ˜ ——šžœžœžœ˜Kšœ.˜.Kšœ4˜4šžœžœ žœ˜Kšœ%˜%Kšœ-˜-K˜—K˜—Kšœ žœžœ˜.Kšœ žœ˜Kšœžœžœžœ˜:šžœ ˜šžœ˜K™Kšžœžœžœžœ˜*Kšžœ žœžœžœ˜0K™Kšžœžœ žœžœ˜@K˜—šžœ˜Kšžœžœžœžœ˜*šžœ žœž˜Kšœžœ˜Kšœžœ˜Kšžœ˜—K˜ K˜šžœžœ˜šžœžœž˜Kš žœžœ žœžœžœ˜?Kšœ˜Kšžœ˜—šžœ žœž˜Kšžœ'žœžœ˜5Kšœ˜Kšžœ˜—K˜—Kšžœ žœžœžœ˜0šžœ žœ˜K˜ K˜šžœžœž˜Kš žœžœ žœžœžœ˜BKšœ˜Kšžœ˜—šžœ žœž˜Kšžœ*žœžœ˜8Kšœ˜Kšžœ˜—K˜—šžœžœ˜Kšžœ žœžœ˜.K˜ K˜šžœžœž˜Kš žœ&žœ žœžœžœ˜NKšœ˜Kšžœ˜—šžœ žœž˜Kšžœ6žœžœ˜DKšœ˜Kšžœ˜—K˜—K˜——Kšœ˜K˜—šœ žœ"˜2K˜—š Ÿœžœžœžœžœ˜7Kšžœžœžœ ˜(Kšœ˜K˜—š Ÿœžœžœžœžœžœ˜@Kšœ²™²Kšœ_™_Kšœžœ˜ K˜Kšœ žœ2˜AKšœžœžœžœ˜Kšœžœ˜ Kšœ žœžœ˜K™ΈK™†K™IK™`šŸ œžœžœ˜Kšžœžœžœ˜0Kšžœ žœG˜ZK™7Kšžœžœžœžœ˜FK™9Kšœ ˜4K˜—šŸ œžœžœ˜!K™½K™’KšœHžœ™LK™`Kšœžœžœ˜K™&Kšžœžœžœ˜0Kšžœ žœG˜ZK™?Kšžœžœžœžœ˜FK™9Kšœ ˜4K™UKšœ !˜8šž˜šžœ žœ˜K˜Kšžœ žœžœ˜Kšœ žœ˜Kšœ(žœžœ ˜IKšœ "˜9Kšžœ˜K˜—šžœžœžœžœ˜%K˜Kšžœ˜K˜—K˜Kšœžœ˜Kšžœ˜—K˜—Kšžœžœžœ˜%šžœžœ˜KšœΟzœ#™7Kšœ žœ˜KšœM˜MKšœ˜Kšœ ˜ Kšœ6˜6K˜—K˜šž˜Kšžœžœžœ˜Kšœ˜šžœž˜Kšœžœ žœ˜(˜ šžœž˜K˜K˜Kšžœ˜—K˜—˜ šžœž˜K˜šœ˜Kšœ™K˜ šœ˜KšœF™F—K˜Kšžœ˜K˜—Kšžœ˜—K˜—˜ šžœ žœ˜Kšœ™K˜šœ˜KšœF™F—K˜K™;Kšžœ˜K˜—Kšžœ˜K˜—Kšžœžœ˜—K˜ Kšžœ˜—K™šžœžœ˜$šžœ˜Kšœd™dKšžœžœ˜,Kšžœ˜Kšœ˜K˜—šž˜Kšžœžœžœ1˜B——Kš žœžœžœ žœžœžœ ˜NKšžœ˜K˜K˜—šŸœžœžœžœžœžœžœ˜GKšžœžœžœ˜šžœžœž˜Kšœ#žœ˜;Kšœ#žœ˜;šžœ˜ šŸœ˜ Kš žœžœžœžœžœ™&šœžœžœž˜K˜Kšžœ˜Kšžœ˜ —K˜—K™Kšœžœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšžœ žœ@˜Pšžœ<ž˜BKšœ7˜7—Kšœ˜Kšžœ˜K˜——K˜—K˜š Ÿ œžœžœžœžœžœ˜3Kšžœžœžœ˜Kšœžœ ˜Bšžœžœ"žœ˜0Kšœ6˜6Kšœ˜K˜—Kšžœ˜K˜K˜—š Ÿ œžœžœžœžœžœ˜9Kšœžœ˜ šžœ žœžœž˜9Kšœžœžœ˜Kšžœ˜—Kšžœžœ˜K˜K˜—š Ÿ œžœžœžœžœžœ˜7Kšžœ1˜7K˜K˜—šŸ œžœžœžœžœžœžœžœ˜]Kšœžœ˜Kšœžœ˜Kšœžœ˜šžœ ž˜Kšœžœ ˜Kšœžœ˜šžœž˜ Kšœžœžœ ˜.Kšœžœ$˜8Kšžœ˜—Kšœ ˜ Kšžœ˜—Kšžœ˜$K˜K˜—šŸœžœžœžœžœžœžœ˜:K™@Kšœžœ˜šžœ ž˜Kšžœžœžœ˜AK˜Kšžœ˜—Kšžœ˜ K˜K˜—š Ÿœžœžœžœžœžœ˜?Kšœžœ˜Kšžœ žœžœ˜Kšžœ˜K˜K™—š Ÿœžœžœžœžœžœ˜5Kšœžœ˜Kšžœžœžœžœ˜)K˜K˜šžœž˜Kšœžœ˜Kšœžœ˜Kšžœ˜—Kšžœžœ˜K˜K™—šŸœžœžœ žœžœžœžœ˜JKšœžœ˜ Kšœ"˜"Kšœ&˜&Kšœ,žœ˜3Kšžœ žœžœžœ˜ Kšžœ<˜BK˜K˜—šŸœžœžœ žœžœžœžœ˜LKšœžœ˜ Kšœžœ˜ Kšœ"˜"Kšœ&˜&Kšœ,žœ˜3Kšžœ žœžœžœ˜Kšœ˜Kšžœ žœžœžœ˜Kšœ.˜.Kšžœ žœžœžœ˜Kšžœ3˜9K˜K˜—šŸœžœžœžœžœžœžœ˜JK™Ršžœ ž˜Kšœžœ#˜*šžœž˜ Kšœžœ˜3Kšžœ˜—Kšžœ˜—Kšžœžœ˜ K˜K™—š Ÿ œžœžœžœžœžœ˜6K™$Kšžœ0˜6K˜K˜—š Ÿœžœžœžœžœžœ˜3K™ΣKšœžœ˜Kšœžœ˜Kšœžœžœ˜šžœ ž˜Kšœžœ˜$šžœž˜ Kšœžœ žœžœ˜CKšžœ˜—K˜Kšžœ˜—Kšžœžœ˜ K˜K˜—šΠbl™K˜š Ÿœžœžœžœžœžœ˜;Kšœ žœ˜ Kšœ žœ˜'Kšœ žœ˜-šžœ$žœž˜>Kšœ/˜/—Kšžœ˜K˜K˜——Kšžœ˜—…—*ΔQ[