PXJ 6IM.Pd6\B-ґ3nLHEȹ\<*vdyl-lʎbHR ept7ђRa+:n;*TPG IE۳ D(A+ ۸q&^?P'ˌ+ME8]>Be-iF}zrE kG]A<y 3e _|vUl.&!.r<\RW 7,F0`$}Ud_=M`ᎭP6|s[/~{6ݧsk$%y|JJzr % {J"u'5䯈e8r**;߶*NxF둋b|-.E̠+$=7V3wj'X<[;kr<8Amsl?HSx%k8DQ$,*cj6:qҁfkEX=atm׋AXҥωJd}ʃH`ol6-O_; 7+ᐏUI]qyrO8+.к#ǽʎ:y*UaD< m&{tU^_k#BDT0)Y-#o(~﮹/Qz^ CFo9#xZ%Vp7F\ .Ѿ]tۏi$`\B )bd0N>ƃ3FL/0# Q2i./)MǍtQ 1l8Ug81gDŽ)SS[᭯C bP$r0ؖ$LmuQ(EYd}ZI?-Ѩd Q*dm+T RU%c:OZC*]7%_ E{y.Kf\ydefJ @dkA,@LpIkDv`imWő@}r'e):h FL{e,*Zgmiun0 m~vt!Y2ԬL,[@o˶I)l>wdYS!cE[*7gPdJEC')=^R㌾ mc5td2Q`9CadV=4K%N\Let`CtZU};Fx!9u"~zQnrP9[BDW_C@噹}Z˂Ѳ,¬Qo}4 qX+eu<yS_J u[d ~n2.I܁3>O_=ƕ,)w -Y3Ԃ<d.Tq}!;?ZswMmIEؔ]'w,"a8 RUwLvO頀di3ݎ}xCfog?D˾G Fތ锎4A0 . ?z~W 3[<J\:},3hsn!*" tת8*`dc,H3. Mupta?@f2M2GOBIU.\0[6f2-j}'r-ɈB!a=$ ؔg[Ay[&r?5)?SݙN3M"GÑh/~m%1w\~_ Eo)beNbwe8BF>R ˫-%)s)6K%lsb$1EjM?> XF~jV>֫o OI]'GIh+eqګXPɯ9;#t^7<\~j~ݙ(X=D1~7boTNϜ`ΰov10qRT-|Q$sS~ifz#`b 6bN-|\ST8y >YRօк58|l ۙ\&ˢ!-ؗk R!p*KJ ޘ0߼lyjlQ$+:. L=boƖWyXDcGә4}Bc\z S#a]-HIw &D .2Bbn"%x F@Yr_9':?O !bW6q,;ҙpK8 gn[q50-O_; 7+ᐏ Dʏ4$^DBӕVaߧQ/M-|Q$sSM%<0 ;qy) <\j!uJ ]9{f;l3R=# v ~a.?b()?Roy\C$O'-+r?ܼ6APK?pK&/P yN}/TOmiJՠLdDv5meter * 10 + $this->char; $this->getChar(); } // If no parameter present, assume control word's default (usually 1) // If no default then assign 0 to the parameter if ($parameter === null) { $parameter = 1; } // Convert parameter to a negative number when applicable if ($negative) { $parameter = -$parameter; } // Update uc value if ($word == "uc") { array_pop($this->uc); $this->uc[] = $parameter; } // Skip space delimiter if (!$this->isSpaceDelimiter()) { $this->pos--; } // If this is \u, then the parameter will be followed // by {$this->uc} characters. if ($word == "u") { // Convert parameter to unsigned decimal unicode if ($negative) { $parameter = 65536 + $parameter; } // Will ignore replacement characters $uc times $uc = end($this->uc); while ($uc > 0) { $this->getChar(); // If the replacement character is encoded as // hexadecimal value \'hh then jump over it if ($this->char == "\\" && $this->rtf[$this->pos] == '\'') { $this->pos = $this->pos + 3; } elseif ($this->char == '{' || $this->char == '{') { // Break if it's an RTF scope delimiter break; } // - To include an RTF delimiter in skippable data, it must be // represented using the appropriate control symbol (that is, // escaped with a backslash,) as in plain text. // // - Any RTF control word or symbol is considered a single character // for the purposes of counting skippable characters. For this reason // it's more appropriate to create a $skip flag and let the Parse() // function take care of the skippable characters. $uc--; } } // Add new RTF word as a child to the current group. $rtfword = new ControlWord(); $rtfword->word = $word; $rtfword->parameter = $parameter; array_push($this->group->children, $rtfword); } /** * Parse ControlSymbol element * * @return void */ protected function parseControlSymbol() { // Read symbol (one character only). $this->getChar(); $symbol = $this->char; // Exceptional case: // Treat EOL symbols as \par control word if ($this->isEndOfLine()) { $rtfword = new ControlWord(); $rtfword->word = 'par'; $rtfword->parameter = 0; array_push($this->group->children, $rtfword); return; } // Symbols ordinarily have no parameter. However, // if this is \' (a single quote), then it is // followed by a 2-digit hex-code: $parameter = 0; if ($symbol == '\'') { $this->getChar(); $parameter = $this->char; $this->getChar(); $parameter = hexdec($parameter . $this->char); } // Add new control symbol as a child to the current group: $rtfsymbol = new ControlSymbol(); $rtfsymbol->symbol = $symbol; $rtfsymbol->parameter = $parameter; array_push($this->group->children, $rtfsymbol); } /** * Parse Control element * * @return void */ protected function parseControl() { // Beginning of an RTF control word or control symbol. // Look ahead by one character to see if it starts with // a letter (control world) or another symbol (control symbol): $this->GetChar(); $this->pos--; // (go back after look-ahead) if ($this->isLetter()) { $this->parseControlWord(); } else { $this->parseControlSymbol(); } } /** * Parse Text element * * @return void */ protected function parseText() { // Parse plain text up to backslash or brace, // unless escaped. $text = ''; $terminate = false; do { // Ignore EOL characters if ($this->char == "\r" || $this->char == "\n") { $this->getChar(); continue; } // Is this an escape? if ($this->char == "\\") { // Perform lookahead to see if this // is really an escape sequence. $this->getChar(); switch ($this->char) { case "\\": break; case '{': break; case '}': break; default: // Not an escape. Roll back. $this->pos = $this->pos - 2; $terminate = true; break; } } elseif ($this->char == '{' || $this->char == '}') { $this->pos--; $terminate = true; } if (!$terminate) { // Save plain text $text .= $this->char; $this->getChar(); } } while (!$terminate && $this->pos < $this->len); // Create new Text element: $text = new Text($text); // If there is no current group, then this is not a valid RTF file. // Throw an exception. if ($this->group == null) { throw new \Exception("Parse error: RTF text outside of group."); } // Add text as a child to the current group: array_push($this->group->children, $text); } /** * Attempt to parse an RTF string. * * @param string $rtf RTF content * * @return void */ protected function parse($rtf) { $this->rtf = $rtf; $this->pos = 0; $this->len = strlen($this->rtf); $this->group = null; $this->root = null; while ($this->pos < $this->len-1) { // Read next character: $this->getChar(); // Ignore \r and \n if ($this->char == "\n" || $this->char == "\r") { continue; } // What type of character is this? switch ($this->char) { case '{': $this->parseStartGroup(); break; case '}': $this->parseEndGroup(); break; case "\\": $this->parseControl(); break; default: $this->parseText(); break; } } } /** * Returns string representation of the document for debug purposes. * * @return string */ public function __toString() { if (!$this->root) { return "No root group"; } return $this->root->toString(); } }