0b313d5e269d7f01bc4818d46ab874ba22241542
[lhc/web/wiklou.git] / includes / Tokenizer.php
1 <?php
2 class Tokenizer {
3 /* private */ var $mText, # Text to be processed by the tokenizer
4 $mPos, # current position of tokenizer in text
5 $mTextLength, # Length of $mText
6 $mQueuedToken; # Tokens that were already found, but not
7 # returned yet.
8
9 /* private */ function Tokenizer()
10 {
11 global $wgLang;
12
13 $this->mPos=0;
14 $this->mTokenQueue=array();
15 $this->linkPrefixExtension = $wgLang->linkPrefixExtension();
16 }
17
18 # factory function
19 function newFromString( $s )
20 {
21 $fname = "Tokenizer::newFromString";
22 wfProfileIn( $fname );
23
24 $t = new Tokenizer();
25 $t->mText = $s;
26 $t->mTextLength = strlen( $s );
27
28 wfProfileOut( $fname );
29 return $t;
30 }
31
32
33 // Return the next token, but do not increase the pointer. The next call
34 // to previewToken or nextToken will return the same token again.
35 // Actually, the pointer is increased, but the token is queued. The next
36 // call to previewToken or nextToken will check the queue and return
37 // the stored token.
38 function previewToken()
39 {
40 $fname = "Tokenizer::previewToken";
41 wfProfileIn( $fname );
42
43 if ( count( $this->mQueuedToken ) != 0 ) {
44 // still one token from the last round around. Return that one first.
45 $token = $this->mQueuedToken[0];
46 } else {
47 $token = $this->nextToken();
48 array_unshift( $this->mQueuedToken, $token );
49 }
50
51 wfProfileOut( $fname );
52 return $token;
53 }
54
55
56 // get the next token
57 // proceeds character by character through the text, looking for characters needing
58 // special attention. Those are currently: I, R, ', [, ], newline
59 //
60 // TODO: handling of French blanks not yet implemented
61 function nextToken()
62 {
63 $fname = "Tokenizer::nextToken";
64 wfProfileIn( $fname );
65
66 if ( count( $this->mQueuedToken ) != 0 ) {
67 // still one token from the last round around. Return that one first.
68 $token = array_shift( $this->mQueuedToken );
69 } else if ( $this->mPos > $this->mTextLength ) {
70 // If no text is left, return "false".
71 $token = false;
72 } else {
73
74 $token["text"]="";
75 $token["type"]="text";
76
77 while ( $this->mPos <= $this->mTextLength ) {
78 switch ( @$ch = $this->mText[$this->mPos] ) {
79 case 'R': // for "RFC "
80 if ( $this->continues("FC ") ) {
81 $queueToken["type"] = $queueToken["text"] = "RFC ";
82 $this->mQueuedToken[] = $queueToken;
83 $this->mPos += 3;
84 break 2; // switch + while
85 }
86 break;
87 case 'I': // for "ISBN "
88 if ( $this->continues("SBN ") ) {
89 $queueToken["type"] = $queueToken["text"] = "ISBN ";
90 $this->mQueuedToken[] = $queueToken;
91 $this->mPos += 4;
92 break 2; // switch + while
93 }
94 break;
95 case "[": // for links "[["
96 if ( $this->continues("[[") ) {
97 $queueToken["type"] = "[[[";
98 $queueToken["text"] = "";
99 $this->mQueuedToken[] = $queueToken;
100 $this->mPos += 3;
101 break 2; // switch + while
102 } else if ( $this->continues("[") ) {
103 $queueToken["type"] = "[[";
104 $queueToken["text"] = "";
105 // Check for a "prefixed link", e.g. Al[[Khazar]]
106 // Mostly for arabic wikipedia
107 if ( $this->linkPrefixExtension ) {
108 while ( $this->linkPrefixExtension
109 && ($len = strlen( $token["text"] ) ) > 0
110 && !ctype_space( $token["text"][$len-1] ) )
111 {
112 //prepend the character to the link's open tag
113 $queueToken["text"] = $token["text"][$len-1] . $queueToken["text"];
114 //remove character from the end of the text token
115 $token["text"] = substr( $token["text"], 0, -1);
116 }
117 }
118 $this->mQueuedToken[] = $queueToken;
119 $this->mPos += 2;
120 break 2; // switch + while
121 }
122 break;
123 case "]": // for end of links "]]"
124 if ( $this->continues("]") ) {
125 $queueToken["type"] = "]]";
126 $queueToken["text"] = "";
127 $this->mQueuedToken[] = $queueToken;
128 $this->mPos += 2;
129 break 2; // switch + while
130 }
131 break;
132 case "'": // for all kind of em's and strong's
133 if ( $this->continues("'") ) {
134 $queueToken["type"] = "'";
135 $queueToken["text"] = "";
136 while( ($this->mPos+1 < $this->mTextLength)
137 && $this->mText[$this->mPos+1] == "'" )
138 {
139 $queueToken["type"] .= "'";
140 $queueToken["pos"] = $this->mPos;
141 $this->mPos ++;
142 }
143
144 $this->mQueuedToken[] = $queueToken;
145 $this->mPos ++;
146 break 2; // switch + while
147 }
148 break;
149 case "\n": // for block levels, actually, only "----" is handled.
150 case "\r":
151 if ( $this->continues( "----" ) )
152 {
153 $queueToken["type"] = "----";
154 $queueToken["text"] = "";
155 $this->mQueuedToken[] = $queueToken;
156 $this->mPos += 5;
157 while ( $this->mPos<$this->mTextLength
158 and $this->mText[$this->mPos] == "-" )
159 {
160 $this->mPos ++;
161 }
162 break 2;
163 }
164 break;
165 case "!": // French spacing rules have a space before exclamation
166 case "?": // and question marks. Those have to become &nbsp;
167 case ":": // And colons, Hashar says ...
168 if ( $this->preceeded( " " ) )
169 {
170 // strip blank from Token
171 $token["text"] = substr( $token["text"], 0, -1 );
172 $queueToken["type"] = "blank";
173 $queueToken["text"] = " {$ch}";
174 $this->mQueuedToken[] = $queueToken;
175 $this->mPos ++;
176 break 2; // switch + while
177 }
178 break;
179 case "0": // A space between two numbers is used to ease reading
180 case "1": // of big numbers, e.g. 1 000 000. Those spaces need
181 case "2": // to be unbreakable
182 case "3":
183 case "4":
184 case "5":
185 case "6":
186 case "7":
187 case "8":
188 case "9":
189 if ( ($this->mTextLength >= $this->mPos +2)
190 && ($this->mText[$this->mPos+1] == " ")
191 && ctype_digit( $this->mText[$this->mPos+2] ) )
192 {
193 $queueToken["type"] = "blank";
194 $queueToken["text"] = $ch . " ";
195 $this->mQueuedToken[] = $queueToken;
196 $this->mPos += 2;
197 break 2; // switch + while
198 }
199 break;
200 case "\302": // first byte of UTF-8 Character Guillemet-left
201 if ( $this->continues( "\253 ") ) // second byte and a blank
202 {
203 $queueToken["type"] = "blank";
204 $queueToken["text"] = "\302\253 ";
205 $this->mQueuedToken[] = $queueToken;
206 $this->mPos += 3;
207 break 2; // switch + while
208 }
209 break;
210 case "\273": //last byte of UTF-8 Character Guillemet-right
211 if ( $this->preceeded( " \302" ) )
212 {
213 $queueToken["type"] = "blank";
214 $queueToken["text"] = " \302\273";
215 $token["text"] = substr( $token["text"], 0, -2 );
216 $this->mQueuedToken[] = $queueToken;
217 $this->mPos ++;
218 break 2; // switch + while
219 }
220 break;
221 case "&": //extensions like <timeline>, since HTML stripping has already been done,
222 //those look like &lt;timeline&gt;
223 if ( $this->continues( "lt;timeline&gt;" ) )
224 {
225 $queueToken["type"] = "<timeline>";
226 $queueToken["text"] = "&lt;timeline&gt;";
227 $this->mQueuedToken[] = $queueToken;
228 $this->mPos += 16;
229 break 2; // switch + while
230 }
231 break;
232
233 } /* switch */
234 $token["text"].=$ch;
235 $this->mPos ++;
236 // echo $this->mPos . "<br>\n";
237 } /* while */
238 } /* if (nothing left in queue) */
239
240 wfProfileOut( $fname );
241 return $token;
242 }
243
244 // function continues
245 // checks whether the mText continues with $cont from mPos+1
246 /* private */ function continues( $cont )
247 {
248 // If string is not long enough to contain $cont, return false
249 if ( $this->mTextLength < $this->mPos + strlen( $cont ) )
250 return false;
251 for ( $i=0; $i < strlen( $cont ); $i++ )
252 {
253 if ( $this->mText[$this->mPos+1+$i] != $cont[$i] )
254 return false;
255 }
256 return true;
257 }
258
259 // function preceeded
260 // checks whether the mText is preceeded by $prec at position mPos
261 /* private */ function preceeded( $prec )
262 {
263 $len = strlen( $prec );
264 // if $prec is longer than the text up to mPos, return false
265 if ( $this->mPos < $len )
266 return false;
267 return ( 0 == strcmp( $prec, substr($this->mText, $this->mPos-$len, $len) ) );
268 }
269
270 function readAllUntil( $border )
271 {
272 $n = strpos( $this->mText, $border, $this->mPos );
273 if ( $n === false )
274 return "";
275 $ret = substr( $this->mText, $this->mPos, $n - $this->mPos );
276 $this->mPos = $n + strlen( $border ) + 1;
277 return $ret;
278 }
279
280 }
281