DIRECTORY ARAccess USING [AppendChar], Ascii USING [CR, LF, NUL, SP, TAB], CmFile USING [ErrorCode, Handle, noMatch, TableDesc], IO USING [Close, EndOfStream, GetChar, GetIndex, SetIndex, STREAM], PFS USING [Close, Error, Open, OpenFile, PathFromRope, StreamFromOpenFile], Rope USING [Compare, Concat, Equal, Fetch, IsEmpty, Length, ROPE, Substr], Token USING [ Filtered, FilterProcType, GetCharProcType, Handle, Line, nonQuote, Object, QuoteProcType--, WhiteSpace--]; CmFilesA: CEDAR MONITOR IMPORTS ARAccess, --Event, Heap, --IO, --MFile, MStream, --PFS, Rope--, Stream, String, StringLookUp, --Supervisor, System--, Token--, Volume EXPORTS CmFile = BEGIN lineSize: CARDINAL = 250; noSection: LONG CARDINAL = LAST[LONG CARDINAL]; sectionHints: HintHandle ¬ NIL; userCm: IO.STREAM ¬ NIL; --MStream.Handle ¬ NIL; readingCount: CARDINAL ¬ 0; resetStream: BOOLEAN ¬ FALSE; HintHandle: TYPE = REF HintObject; HintObject: TYPE = RECORD [ name: Rope.ROPE ¬ NIL, position: LONG CARDINAL ¬ 0, next: HintHandle ¬ NIL]; Handle: TYPE = REF Object; Object: TYPE = MACHINE DEPENDENT RECORD [ tokenData: Token.Object, sh: IO.STREAM, --Stream.Handle, specificIndex: LONG CARDINAL ¬ noSection]; AbsToRep: PROCEDURE [h: CmFile.Handle] RETURNS [Handle] = TRUSTED {RETURN[LOOPHOLE[h]]}; RepToAbs: PROCEDURE [h: Handle] RETURNS [CmFile.Handle] = { RETURN[NEW[Token.Object ¬ h.tokenData]]}; Validate: PROCEDURE [h: CmFile.Handle] RETURNS [repH: Handle] = BEGIN repH ¬ AbsToRep[h]; IF repH.tokenData.getChar # GetChar THEN ERROR Error[invalidHandle]; END; beginSectionName: CHARACTER = '[; commentChar: CHARACTER = '-; endSectionName: CHARACTER = ']; nameBreak: CHARACTER = ':; separateSectionName: CHARACTER = ':; Error: PUBLIC SIGNAL [code: CmFile.ErrorCode] = CODE; TableError: PUBLIC SIGNAL [h: CmFile.Handle, name: Rope.ROPE] = CODE; emptyKey: CARD = CmFile.noMatch - 1; ambiguous: CARD = emptyKey - 1; UpperCase: PROCEDURE[c: CHAR] RETURNS[CHAR] ~{ IF c <= 'z AND c >= 'a THEN RETURN[c - 'a + 'A]; RETURN[c]; }; InitialInternal: PUBLIC PROCEDURE [ key, entry: Rope.ROPE, maxLength: CARDINAL, caseFold: BOOLEAN ¬ TRUE] RETURNS [matchLength: CARDINAL] = { matchLength ¬ 0; IF caseFold THEN DO IF UpperCase[key.Fetch[matchLength]] # UpperCase[entry.Fetch[matchLength]] THEN EXIT; IF (matchLength ¬ matchLength + 1) = maxLength THEN EXIT; ENDLOOP ELSE DO IF key.Fetch[matchLength] # entry.Fetch[matchLength] THEN EXIT; IF (matchLength ¬ matchLength + 1) = maxLength THEN EXIT; ENDLOOP; }; InTable: PUBLIC PROCEDURE [key: Rope.ROPE, table: CmFile.TableDesc, caseFold: BOOLEAN ¬ TRUE, noAbbreviation: BOOLEAN ¬ FALSE] RETURNS [index: CARDINAL] = { keyLength: INT; IF Rope.IsEmpty[key] THEN {index ¬ emptyKey; GOTO Return}; index ¬ CmFile.noMatch; keyLength ¬ key.Length[]; FOR i: CARDINAL IN [0..table.len) DO entry: Rope.ROPE = table[i]; lengthsAreSame: BOOLEAN; SELECT entry.Length[] FROM < keyLength => LOOP; -- empty entry takes this arm -- keyLength => lengthsAreSame ¬ TRUE; ENDCASE => IF noAbbreviation THEN LOOP ELSE lengthsAreSame ¬ FALSE; { leng: INT ¬ InitialInternal[key, entry, keyLength, caseFold]; SELECT leng FROM < keyLength => NULL; keyLength => { IF lengthsAreSame THEN {index ¬ i; EXIT}; index ¬ (IF index = CmFile.noMatch THEN i ELSE ambiguous); }; ENDCASE; }; ENDLOOP; EXITS Return => NULL; }; FreeString: PUBLIC PROCEDURE [s: Rope.ROPE] RETURNS [nil: Rope.ROPE ¬ NIL] = {--Heap.systemZone.FREE[@s]--}; Close: PUBLIC PROCEDURE [h: CmFile.Handle] RETURNS [nil: CmFile.Handle] = { repH: Handle ¬ Validate[h]; repH.sh.Close --delete--[--repH.sh--]; repH.tokenData.getChar ¬ NIL; -- make sure deallocated object will not look valid RETURN[h]}; Open: PUBLIC PROCEDURE [fileName: Rope.ROPE] RETURNS [h: CmFile.Handle] = BEGIN repH: Handle; BEGIN fh: PFS.OpenFile; --MFile.Handle; sh: IO.STREAM; --Stream.Handle; fh ¬ PFS.Open[ PFS.PathFromRope[fileName], read ! PFS.Error => GOTO Lose]; sh ¬ PFS.StreamFromOpenFile[fh, read ! PFS.Error => {PFS.Close[fh]; GOTO Lose}]; repH ¬ NEW[ Object ¬ [tokenData: [getChar: GetChar, break: Ascii.NUL ], sh: sh]]; EXITS Lose => {SIGNAL Error[fileNotFound]; RETURN[NIL]}; END; h ¬ RepToAbs[repH]; END; ReadLineOrToken: PUBLIC PROCEDURE [ h: Token.Handle, buffer: Rope.ROPE, terminator: CHARACTER] = TRUSTED BEGIN buffer ¬ NIL; --buffer.length ¬ 0; WHILE SELECT (h.break ¬ h.getChar[h]) FROM Ascii.SP, Ascii.TAB => TRUE, ENDCASE => FALSE DO ENDLOOP; DO SELECT h.break FROM terminator, Ascii.CR, Ascii.NUL => EXIT; ENDCASE; buffer ¬ ARAccess.AppendChar[buffer, h.break]; h.break ¬ h.getChar[h]; ENDLOOP; END; TitleMatch: PUBLIC PROCEDURE [buffer, title: Rope.ROPE] RETURNS [BOOLEAN] = BEGIN llSubString, titleSubString: Rope.ROPE; --String.SubStringDescriptor; IF buffer.Length < title.Length + 2 OR buffer.Fetch[0] # beginSectionName OR buffer.Fetch[title.Length + 1] # endSectionName THEN RETURN[FALSE]; llSubString ¬ Rope.Substr[buffer, 1, title.Length]; titleSubString ¬ Rope.Substr[title, 0, title.Length]; RETURN[Rope.Equal[llSubString, titleSubString]]; END; WhiteSpace: PUBLIC Token.FilterProcType = { RETURN[WhiteSpaceInline[c]]; }; WhiteSpaceInline: PROCEDURE [c: CHAR] RETURNS [ --isWhiteSpace:-- BOOLEAN] = INLINE { RETURN[SELECT c FROM Ascii.SP, Ascii.TAB, Ascii.CR, Ascii.LF => TRUE, ENDCASE => FALSE]; }; FindSection: PUBLIC PROCEDURE [h: CmFile.Handle, title: Rope.ROPE] RETURNS [opened: BOOLEAN] = BEGIN localLine: Rope.ROPE ¬ NIL; --[lineSize]; repH: Handle = Validate[h]; genericIndex: LONG CARDINAL ¬ noSection; qualifiedTitle: Rope.ROPE; localLine ¬ ARAccess.AppendChar[localLine, separateSectionName]; localLine ¬ localLine.Concat[title]; qualifiedTitle ¬ localLine; IF IsUserDotCm[repH] THEN { str: Rope.ROPE; doneOne: BOOLEAN ¬ FALSE; temp: HintHandle; SELECT Rope.Compare[qualifiedTitle, title] FROM less => str ¬ qualifiedTitle; greater => str ¬ title; ENDCASE; FOR l: HintHandle ¬ sectionHints, temp UNTIL l = NIL DO SELECT Rope.Compare[str, l.name] FROM equal => { IF str = title THEN genericIndex ¬ l.position ELSE repH.specificIndex ¬ l.position; IF doneOne THEN EXIT; temp ¬ l.next}; greater => {temp ¬ l.next; LOOP}; ENDCASE => temp ¬ l; -- didn't find first title, so advance to second title IF doneOne THEN EXIT; doneOne ¬ TRUE; IF str = qualifiedTitle THEN str ¬ title ELSE str ¬ qualifiedTitle; ENDLOOP; } ELSE { IO.SetIndex[repH.sh, 0]; DO ENABLE UNWIND => NULL; --Heap.systemZone.FREE[@qualifiedTitle]; ReadLine[h, localLine]; IF h.break = Ascii.NUL THEN EXIT; IF localLine.Length # 0 AND localLine.Fetch[0] = beginSectionName THEN BEGIN IF TitleMatch[localLine, qualifiedTitle] THEN BEGIN repH.specificIndex ¬ IO.GetIndex[repH.sh]; IF genericIndex # noSection THEN EXIT; END; IF TitleMatch[localLine, title] THEN BEGIN genericIndex ¬ IO.GetIndex[repH.sh]; IF repH.specificIndex # noSection THEN EXIT; END; END; ENDLOOP}; IF opened ¬ (genericIndex # noSection) THEN IO.SetIndex[repH.sh, genericIndex] ELSE opened ¬ ~TrySpecific[h]; END; TrySpecific: PROCEDURE [h: CmFile.Handle] RETURNS [alreadyDone: BOOLEAN] = BEGIN repH: Handle = Validate[h]; IF ~(alreadyDone ¬ (repH.specificIndex = noSection)) THEN { IO.SetIndex[repH.sh, repH.specificIndex]; repH.specificIndex ¬ noSection}; END; NextItem: PUBLIC PROCEDURE [h: CmFile.Handle] RETURNS [name, value: Rope.ROPE] = TRUSTED BEGIN localLine: Rope.ROPE ¬ NIL; --[lineSize]; DO ReadName[h, localLine]; IF h.break = nameBreak THEN EXIT; IF TrySpecific[h] THEN RETURN[NIL, NIL]; ENDLOOP; name ¬ localLine; value ¬ Token.Filtered[h: h, data: NIL, filter: Token.Line, skip: whiteSpace, temporary: FALSE]; FOR i: CARDINAL DECREASING IN [0..value.Length) DO IF ~WhiteSpace[value.Fetch[i], NIL] THEN { value ¬ Rope.Substr[value, 0, i+1]; EXIT }; ENDLOOP; END; FindItem: PUBLIC PROCEDURE [h: CmFile.Handle, title, name: Rope.ROPE] RETURNS [found: BOOLEAN] = BEGIN localLine: Rope.ROPE ¬ NIL; --[lineSize]; IF ~FindSection[h, title] THEN RETURN[FALSE]; DO GobbleRestOfLine[h]; -- consume any parts of preceding line left over by client ReadName[h, localLine]; IF h.break = nameBreak THEN SELECT TRUE FROM Rope.Equal[name, localLine] => RETURN[TRUE]; ENDCASE => LOOP; IF TrySpecific[h] THEN RETURN[FALSE]; ENDLOOP; END; NextValue: PUBLIC PROCEDURE [h: CmFile.Handle, table: CmFile.TableDesc] RETURNS [index: CARDINAL] = BEGIN localLine: Rope.ROPE ¬ NIL; --[lineSize]; DO GobbleRestOfLine[h]; -- consume any parts of preceding line left over by client ReadName[h, localLine]; IF h.break # nameBreak THEN IF TrySpecific[h] THEN RETURN[CmFile.noMatch] ELSE {h.break ¬ Ascii.CR; LOOP}; index ¬ InTable[localLine, table, TRUE, TRUE].index; IF index < table.len THEN EXIT; SIGNAL TableError[h, localLine]; ENDLOOP; END; GetChar: PRIVATE Token.GetCharProcType = BEGIN repH: Handle = AbsToRep[h]; c ¬ IO.GetChar[repH.sh ! IO.EndOfStream => {c ¬ Ascii.NUL; CONTINUE}] END; GobbleRestOfLine: PRIVATE PROCEDURE [h: CmFile.Handle] = TRUSTED BEGIN WHILE SELECT h.break FROM Ascii.CR, Ascii.NUL => FALSE, ENDCASE => TRUE DO h.break ¬ h.getChar[h]; ENDLOOP; END; IsCommentLine: PRIVATE PROCEDURE [s: Rope.ROPE] RETURNS [BOOLEAN] = BEGIN RETURN[s.Length > 1 AND s.Fetch[0] = commentChar AND s.Fetch[1] = commentChar]; END; IsUserDotCm: PRIVATE PROCEDURE [repH: Handle] RETURNS [BOOLEAN] = BEGIN RETURN [FALSE]; END; ReadLine: PRIVATE PROCEDURE [h: CmFile.Handle, buffer: Rope.ROPE] = BEGIN DO ReadLineOrToken[h, buffer, Ascii.CR]; IF h.break # Ascii.CR OR ~IsCommentLine[buffer] THEN EXIT; ENDLOOP; END; ReadName: PRIVATE PROCEDURE [h: CmFile.Handle, buffer: Rope.ROPE] = BEGIN DO ReadLineOrToken[h, buffer, nameBreak]; IF h.break = Ascii.NUL THEN EXIT; IF buffer.Length # 0 AND ~IsCommentLine[buffer] THEN IF buffer.Fetch[0] = beginSectionName THEN { GobbleRestOfLine[h]; h.break ¬ Ascii.NUL; EXIT} ELSE IF h.break = nameBreak THEN EXIT; GobbleRestOfLine[h]; ENDLOOP; END; CmFileQuote: Token.QuoteProcType = BEGIN RETURN[SELECT c FROM '" => c, ENDCASE => Token.nonQuote]; END; FreeList: INTERNAL PROCEDURE [freeStream: BOOLEAN] = BEGIN sectionHints ¬ NIL; IF freeStream AND userCm # NIL THEN userCm.Close--delete--[--userCm--]; userCm ¬ NIL; END; END. -- of CmFilesA ” Copyright Σ 1981, 1982, 1984 , 1992by Xerox Corporation. All rights reserved. File: CmFilesA.mesa - last edit: SXW , 10-Jul-81 17:46:35 BXM , 16-Jul-81 16:28:10 JGS, 2-Sep-81 9:47:44 Tom, Aug 28, 1979 6:01 PM Mark, 15-Jul-81 11:02:48 PXK , 8-Jan-82 18:20:19 RXR , 13-Oct-81 16:02:24 SXS , 14-Oct-81 10:09:17 LXR , 4-Jan-84 10:55:39 AXD , 21-Sep-82 16:17:38 CXH , 16-Dec-82 17:30:57 Philip James, March 13, 1991 4:51 pm PST Christian Jacobi, April 7, 1992 5:21 pm PDT Event: TYPE USING [fileSystem], EventTypes: TYPE USING [newSearchPath], Heap: TYPE USING [Create, Flush, systemZone], MFile USING [dontRelease, Error, GetProperties, Handle, maxNameLength, ReadOnly, Release], MStream USING [Create, Error, GetFile, Handle, PleaseReleaseProc, ReadOnly], Stream USING [EndOfStream, GetChar, GetPosition, Handle, SetPosition], String USING [ AppendChar, AppendString, Compare, CopyToNewString, EquivalentString, EquivalentSubString, StringBoundsFault, SubStringDescriptor], StringLookUp USING [InTable, noMatch, TableDesc], Supervisor USING [ AddDependency, AgentProcedure, CreateSubsystem, SubsystemHandle], System USING [switches], Volume USING [GetLabelString, systemID]; Global Data agent: Supervisor.SubsystemHandle _ Supervisor.CreateSubsystem[FileSystemEvent]; z: UNCOUNTED ZONE = Heap.Create[initial: 2, increment: 1]; Syntax of a Cm file is: CmFile ::= Section | CmFile Section Section ::= SectionName SectionBody SectionName ::= beginSectionName NameBody endSectionName NameBody ::= NameBody ~endSectionName | ~endSectionName SectionBody ::= SectionBody SectionLine | SectionLine | SectionLine ::= Name nameBreak MiscLine | MiscLine Name ::= Name ~nameBreak | ~nameBreak MiscLines ::= MiscLines MiscLine | MiscLine | Any line that begins with two commentChar's is ignored, as is any SectionLine that is only a MiscLine. SIGNALs From StringLookUpsA Input restrictions: key # NIL AND entry # NIL AND maxLength > 0 AND key.length >= maxLength AND entry.length >= maxLength If table has duplicate entries, only the first one's index might be returned Simple Interface Procedures Heap.systemZone.FREE[@h]; Line: PUBLIC PROCEDURE [fileName, title, name: Rope.ROPE] RETURNS [s: Rope.ROPE] = This is not as quick as it could be, but minimizes code. Note that NextItem will automatically go from generic to specific section. BEGIN h: CmFile.Handle; s _ NIL; h _ Open[fileName ! Error => GOTO NoFile]; IF FindSection[h, title] THEN DO tag, value: Rope.ROPE; [tag, value] _ NextItem[h ! UNWIND => h _ Close[h]]; IF tag = NIL THEN EXIT; IF String.EquivalentString[name, tag] THEN { temp: Rope.ROPE _ s; s _ value; value _ temp}; Heap.systemZone.FREE[@tag]; Heap.systemZone.FREE[@value]; ENDLOOP; h _ Close[h]; EXITS NoFile => NULL; END; IF System.switches['N] = down AND String.EquivalentString[fileName, "User.cm"L] THEN GOTO Lose; Output assertion: h.break is one of terminator, Ascii.CR, or Ascii.NUL Skip over leading white space Now pick up token (or the line) String.AppendChar[ buffer, h.break ! String.StringBoundsFault => GOTO TooMany]; EXITS TooMany => NULL; More efficient ones Volume.GetLabelString[Volume.systemID, localLine]; String.AppendChar[localLine, separateSectionName]; String.AppendString[localLine, title]; qualifiedTitle _ String.CopyToNewString[localLine, Heap.systemZone]; First try to locate both generic and specific sections CheckHints[]; DoneReading[] Heap.systemZone.FREE[@qualifiedTitle]; Process generic section, then specific section name _ String.CopyToNewString[localLine, Heap.systemZone]; value.length _ i+1; Use "User.cm" as fileName in above UserDotCmOpen: PUBLIC PROCEDURE RETURNS [h: CmFile.Handle] = BEGIN h _ Open["User.cm"]; END; UserDotCmLine: PUBLIC PROCEDURE [title, name: Rope.ROPE] RETURNS [s: Rope.ROPE] = BEGIN s _ Line[fileName: "User.cm", title: title, name: name]; END; Private Procedures name: Rope.ROPE _ NIL; --[MFile.maxNameLength]; [] _ MFile.GetProperties[MStream.GetFile[repH.sh], name]; RETURN[Rope.Equal[name, "User.cm"]]; Suppresses comment lines Suppresses comment lines and any line that is neither a SectionName nor a SectionLine Output assertion: h.break = Ascii.NUL OR h.break = nameBreak OR h.getChar is positioned just after Ascii.CR CheckHints: ENTRY PROCEDURE = BEGIN localLine: Rope.ROPE _ [lineSize]; list, l: HintHandle _ NIL; position: LONG CARDINAL; name: Rope.ROPE _ NIL; index: CARDINAL _ 0; tokenHandle: Token.Object _ [getChar, Ascii.NUL]; getChar: Token.GetCharProcType = { c _ Stream.GetChar[userCm ! Stream.EndOfStream => {c _ Ascii.NUL; CONTINUE}]}; IF sectionHints # NIL THEN {readingCount _ readingCount + 1; RETURN}; userCm _ MStream.ReadOnly["User.cm"L, [myRelease, NIL] ! MStream.Error => CONTINUE]; IF userCm = NIL THEN {SIGNAL Error[fileNotFound]; RETURN}; DO ReadLine[@tokenHandle, localLine]; IF tokenHandle.break = Ascii.NUL THEN EXIT; IF localLine.length = 0 OR localLine[0] # beginSectionName THEN LOOP; position _ Stream.GetPosition[userCm]; FOR index IN [1..localLine.length) DO IF localLine[index] = endSectionName THEN EXIT; REPEAT FINISHED => LOOP; ENDLOOP; name _ z.NEW[StringBody[index - 1]]; FOR i: CARDINAL IN [1..index) DO name[i - 1] _ localLine[i] ENDLOOP; name.length _ index - 1; IF list = NIL THEN list _ z.NEW[HintObject _ [name, position, list]] ELSE SELECT String.Compare[name, list.name] FROM -1 => list _ z.NEW[HintObject _ [name, position, list]]; 0 => z.FREE[@name]; ENDCASE => { FOR l _ list, l.next UNTIL l.next = NIL DO SELECT String.Compare[name, l.next.name] FROM -1 => EXIT; 0 => {z.FREE[@name]; EXIT}; ENDCASE; ENDLOOP; IF name # NIL THEN l.next _ z.NEW[HintObject _ [name, position, l.next]]}; ENDLOOP; sectionHints _ list; readingCount _ readingCount + 1; END; DoneReading: ENTRY PROCEDURE = BEGIN readingCount _ readingCount - 1; IF readingCount = 0 AND resetStream THEN {FreeList[TRUE]; resetStream _ FALSE} END; myRelease: ENTRY MStream.PleaseReleaseProc = BEGIN IF readingCount > 0 THEN RETURN[no]; FreeList[FALSE]; RETURN[goAhead] END; FileSystemEvent: ENTRY Supervisor.AgentProcedure = BEGIN IF event # EventTypes.newSearchPath THEN RETURN; IF readingCount > 0 THEN resetStream _ TRUE ELSE FreeList[TRUE]; END; Heap.Flush[z]; main body Supervisor.AddDependency[client: agent, implementor: Event.fileSystem]; Κ˜•NewlineDelimiter –(cedarcode) style™šœ ΟeœC™OJšœ ™ Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ(™(J™+—Icode˜šΟk ˜ Kšœ žœ˜Kš œžœžœžœžœžœ˜#Kšœžœ)˜5Jšœžœžœ™Jšœ žœžœ™'Jšœžœžœ™-Kšžœžœ3žœ˜Cšœžœ™ J™N—Jšœžœ?™LKšžœžœB˜KKšœžœ2žœ ˜JJšœžœ:™Fšœžœ™J™EJ™=—Jšœ žœ™1šœ žœ™J™A—Jšœžœ ™šœžœ˜ K˜9KšœΟcœ˜1—Jšœžœ™(—K˜šΟnœž ˜šž˜Kšœ ŸžœŸžœŸ"˜^KšŸœŸ ˜'—Kšžœ ˜Kšž˜—˜Jšœ ™ —˜Kšœ žœ˜—˜Kš œ žœžœžœžœžœ˜/—˜J™PKšœžœ˜Kš œžœžœžœŸœŸ˜0Kšœžœ˜Kšœ žœžœ˜K˜Jšœž œžœ)™:—˜Kšœ žœžœ ˜"šœ žœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœžœ˜——˜Kšœžœžœ˜š œžœžœž œžœ˜)K˜KšœžœžœŸ˜Kšœžœžœ˜*——˜Kš  œž œžœ žœžœžœ˜Xš œž œ žœ˜;Kšžœžœ˜)—š œž œžœ˜?Kšž˜K˜Kšžœ"žœžœ˜DKšžœ˜——˜Jšœ™Jšœ#™#Jšœ#™#Jšœ8™8Jšœ7™7Jšœ7™7Jšœ2™2Jšœ%™%Jšœ-™-JšœA™AJšœ%™%Kšœž œ˜!Kšœ ž œ˜Kšœž œ˜Kšœ ž œ˜Kšœž œ˜$—˜Jšœ™—˜Kš œžœžœžœ˜5Kš   œžœžœžœžœ˜E—˜J™J™Kšœ žœ˜$Kšœ žœ˜K˜š   œž œžœžœžœ˜.Kšžœ žœ žœžœ˜0Kšžœ˜ K˜—K˜š œžœž œ˜#Kš œžœ žœ žœžœ˜EKšžœžœ˜!Jšœ?™?Jšœ9™9˜K˜šžœ ž˜KšžœIžœžœ˜UKšžœ-žœžœ˜9Kšž˜—šž˜Kšžœ3žœžœ˜?Kšžœ-žœžœ˜9Kšžœ˜—Kšœ˜——K˜š  œžœž œ žœ%žœžœ˜]Kš œžœžœžœ žœ˜