PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/tags/Doduo/WebKitSite/blog/wp-includes/gettext.php

https://github.com/weissms/owb-mirror
PHP | 394 lines | 220 code | 33 blank | 141 comment | 60 complexity | 8bade30d63872754a2c41950feaadab4 MD5 | raw file
  1. <?php
  2. /*
  3. Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
  4. Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
  5. This file is part of PHP-gettext.
  6. PHP-gettext is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2 of the License, or
  9. (at your option) any later version.
  10. PHP-gettext is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with PHP-gettext; if not, write to the Free Software
  16. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. */
  18. /**
  19. * Provides a simple gettext replacement that works independently from
  20. * the system's gettext abilities.
  21. * It can read MO files and use them for translating strings.
  22. * The files are passed to gettext_reader as a Stream (see streams.php)
  23. *
  24. * This version has the ability to cache all strings and translations to
  25. * speed up the string lookup.
  26. * While the cache is enabled by default, it can be switched off with the
  27. * second parameter in the constructor (e.g. whenusing very large MO files
  28. * that you don't want to keep in memory)
  29. */
  30. class gettext_reader {
  31. //public:
  32. var $error = 0; // public variable that holds error code (0 if no error)
  33. //private:
  34. var $BYTEORDER = 0; // 0: low endian, 1: big endian
  35. var $STREAM = NULL;
  36. var $short_circuit = false;
  37. var $enable_cache = false;
  38. var $originals = NULL; // offset of original table
  39. var $translations = NULL; // offset of translation table
  40. var $pluralheader = NULL; // cache header field for plural forms
  41. var $select_string_function = NULL; // cache function, which chooses plural forms
  42. var $total = 0; // total string count
  43. var $table_originals = NULL; // table for original strings (offsets)
  44. var $table_translations = NULL; // table for translated strings (offsets)
  45. var $cache_translations = NULL; // original -> translation mapping
  46. /* Methods */
  47. /**
  48. * Reads a 32bit Integer from the Stream
  49. *
  50. * @access private
  51. * @return Integer from the Stream
  52. */
  53. function readint() {
  54. if ($this->BYTEORDER == 0) {
  55. // low endian
  56. $low_end = unpack('V', $this->STREAM->read(4));
  57. return array_shift($low_end);
  58. } else {
  59. // big endian
  60. $big_end = unpack('N', $this->STREAM->read(4));
  61. return array_shift($big_end);
  62. }
  63. }
  64. /**
  65. * Reads an array of Integers from the Stream
  66. *
  67. * @param int count How many elements should be read
  68. * @return Array of Integers
  69. */
  70. function readintarray($count) {
  71. if ($this->BYTEORDER == 0) {
  72. // low endian
  73. return unpack('V'.$count, $this->STREAM->read(4 * $count));
  74. } else {
  75. // big endian
  76. return unpack('N'.$count, $this->STREAM->read(4 * $count));
  77. }
  78. }
  79. /**
  80. * Constructor
  81. *
  82. * @param object Reader the StreamReader object
  83. * @param boolean enable_cache Enable or disable caching of strings (default on)
  84. */
  85. function gettext_reader($Reader, $enable_cache = true) {
  86. // If there isn't a StreamReader, turn on short circuit mode.
  87. if (! $Reader || isset($Reader->error) ) {
  88. $this->short_circuit = true;
  89. return;
  90. }
  91. // Caching can be turned off
  92. $this->enable_cache = $enable_cache;
  93. // $MAGIC1 = (int)0x950412de; //bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565
  94. $MAGIC1 = (int) - 1794895138;
  95. // $MAGIC2 = (int)0xde120495; //bug
  96. $MAGIC2 = (int) - 569244523;
  97. // 64-bit fix
  98. $MAGIC3 = (int) 2500072158;
  99. $this->STREAM = $Reader;
  100. $magic = $this->readint();
  101. if ($magic == $MAGIC1 || $magic == $MAGIC3) { // to make sure it works for 64-bit platforms
  102. $this->BYTEORDER = 0;
  103. } elseif ($magic == ($MAGIC2 & 0xFFFFFFFF)) {
  104. $this->BYTEORDER = 1;
  105. } else {
  106. $this->error = 1; // not MO file
  107. return false;
  108. }
  109. // FIXME: Do we care about revision? We should.
  110. $revision = $this->readint();
  111. $this->total = $this->readint();
  112. $this->originals = $this->readint();
  113. $this->translations = $this->readint();
  114. }
  115. /**
  116. * Loads the translation tables from the MO file into the cache
  117. * If caching is enabled, also loads all strings into a cache
  118. * to speed up translation lookups
  119. *
  120. * @access private
  121. */
  122. function load_tables() {
  123. if (is_array($this->cache_translations) &&
  124. is_array($this->table_originals) &&
  125. is_array($this->table_translations))
  126. return;
  127. /* get original and translations tables */
  128. $this->STREAM->seekto($this->originals);
  129. $this->table_originals = $this->readintarray($this->total * 2);
  130. $this->STREAM->seekto($this->translations);
  131. $this->table_translations = $this->readintarray($this->total * 2);
  132. if ($this->enable_cache) {
  133. $this->cache_translations = array ();
  134. /* read all strings in the cache */
  135. for ($i = 0; $i < $this->total; $i++) {
  136. $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
  137. $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
  138. $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
  139. $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
  140. $this->cache_translations[$original] = $translation;
  141. }
  142. }
  143. }
  144. /**
  145. * Returns a string from the "originals" table
  146. *
  147. * @access private
  148. * @param int num Offset number of original string
  149. * @return string Requested string if found, otherwise ''
  150. */
  151. function get_original_string($num) {
  152. $length = $this->table_originals[$num * 2 + 1];
  153. $offset = $this->table_originals[$num * 2 + 2];
  154. if (! $length)
  155. return '';
  156. $this->STREAM->seekto($offset);
  157. $data = $this->STREAM->read($length);
  158. return (string)$data;
  159. }
  160. /**
  161. * Returns a string from the "translations" table
  162. *
  163. * @access private
  164. * @param int num Offset number of original string
  165. * @return string Requested string if found, otherwise ''
  166. */
  167. function get_translation_string($num) {
  168. $length = $this->table_translations[$num * 2 + 1];
  169. $offset = $this->table_translations[$num * 2 + 2];
  170. if (! $length)
  171. return '';
  172. $this->STREAM->seekto($offset);
  173. $data = $this->STREAM->read($length);
  174. return (string)$data;
  175. }
  176. /**
  177. * Binary search for string
  178. *
  179. * @access private
  180. * @param string string
  181. * @param int start (internally used in recursive function)
  182. * @param int end (internally used in recursive function)
  183. * @return int string number (offset in originals table)
  184. */
  185. function find_string($string, $start = -1, $end = -1) {
  186. if (($start == -1) or ($end == -1)) {
  187. // find_string is called with only one parameter, set start end end
  188. $start = 0;
  189. $end = $this->total;
  190. }
  191. if (abs($start - $end) <= 1) {
  192. // We're done, now we either found the string, or it doesn't exist
  193. $txt = $this->get_original_string($start);
  194. if ($string == $txt)
  195. return $start;
  196. else
  197. return -1;
  198. } else if ($start > $end) {
  199. // start > end -> turn around and start over
  200. return $this->find_string($string, $end, $start);
  201. } else {
  202. // Divide table in two parts
  203. $half = (int)(($start + $end) / 2);
  204. $cmp = strcmp($string, $this->get_original_string($half));
  205. if ($cmp == 0)
  206. // string is exactly in the middle => return it
  207. return $half;
  208. else if ($cmp < 0)
  209. // The string is in the upper half
  210. return $this->find_string($string, $start, $half);
  211. else
  212. // The string is in the lower half
  213. return $this->find_string($string, $half, $end);
  214. }
  215. }
  216. /**
  217. * Translates a string
  218. *
  219. * @access public
  220. * @param string string to be translated
  221. * @return string translated string (or original, if not found)
  222. */
  223. function translate($string) {
  224. if ($this->short_circuit)
  225. return $string;
  226. $this->load_tables();
  227. if ($this->enable_cache) {
  228. // Caching enabled, get translated string from cache
  229. if (array_key_exists($string, $this->cache_translations))
  230. return $this->cache_translations[$string];
  231. else
  232. return $string;
  233. } else {
  234. // Caching not enabled, try to find string
  235. $num = $this->find_string($string);
  236. if ($num == -1)
  237. return $string;
  238. else
  239. return $this->get_translation_string($num);
  240. }
  241. }
  242. /**
  243. * Get possible plural forms from MO header
  244. *
  245. * @access private
  246. * @return string plural form header
  247. */
  248. function get_plural_forms() {
  249. // lets assume message number 0 is header
  250. // this is true, right?
  251. $this->load_tables();
  252. // cache header field for plural forms
  253. if (! is_string($this->pluralheader)) {
  254. if ($this->enable_cache) {
  255. $header = $this->cache_translations[""];
  256. } else {
  257. $header = $this->get_translation_string(0);
  258. }
  259. $header .= "\n"; //make sure our regex matches
  260. if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
  261. $expr = $regs[1];
  262. else
  263. $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
  264. // add parentheses
  265. // important since PHP's ternary evaluates from left to right
  266. $expr.= ';';
  267. $res= '';
  268. $p= 0;
  269. for ($i= 0; $i < strlen($expr); $i++) {
  270. $ch= $expr[$i];
  271. switch ($ch) {
  272. case '?':
  273. $res.= ' ? (';
  274. $p++;
  275. break;
  276. case ':':
  277. $res.= ') : (';
  278. break;
  279. case ';':
  280. $res.= str_repeat( ')', $p) . ';';
  281. $p= 0;
  282. break;
  283. default:
  284. $res.= $ch;
  285. }
  286. }
  287. $this->pluralheader = $res;
  288. }
  289. return $this->pluralheader;
  290. }
  291. /**
  292. * Detects which plural form to take
  293. *
  294. * @access private
  295. * @param n count
  296. * @return int array index of the right plural form
  297. */
  298. function select_string($n) {
  299. if (is_null($this->select_string_function)) {
  300. $string = $this->get_plural_forms();
  301. if (preg_match("/nplurals\s*=\s*(\d+)\s*\;\s*plural\s*=\s*(.*?)\;+/", $string, $matches)) {
  302. $nplurals = $matches[1];
  303. $expression = $matches[2];
  304. $expression = str_replace("n", '$n', $expression);
  305. } else {
  306. $nplurals = 2;
  307. $expression = ' $n == 1 ? 0 : 1 ';
  308. }
  309. $func_body = "
  310. \$plural = ($expression);
  311. return (\$plural <= $nplurals)? \$plural : \$plural - 1;";
  312. $this->select_string_function = create_function('$n', $func_body);
  313. }
  314. return call_user_func($this->select_string_function, $n);
  315. }
  316. /**
  317. * Plural version of gettext
  318. *
  319. * @access public
  320. * @param string single
  321. * @param string plural
  322. * @param string number
  323. * @return translated plural form
  324. */
  325. function ngettext($single, $plural, $number) {
  326. if ($this->short_circuit) {
  327. if ($number != 1)
  328. return $plural;
  329. else
  330. return $single;
  331. }
  332. // find out the appropriate form
  333. $select = $this->select_string($number);
  334. // this should contains all strings separated by NULLs
  335. $key = $single.chr(0).$plural;
  336. if ($this->enable_cache) {
  337. if (! array_key_exists($key, $this->cache_translations)) {
  338. return ($number != 1) ? $plural : $single;
  339. } else {
  340. $result = $this->cache_translations[$key];
  341. $list = explode(chr(0), $result);
  342. return $list[$select];
  343. }
  344. } else {
  345. $num = $this->find_string($key);
  346. if ($num == -1) {
  347. return ($number != 1) ? $plural : $single;
  348. } else {
  349. $result = $this->get_translation_string($num);
  350. $list = explode(chr(0), $result);
  351. return $list[$select];
  352. }
  353. }
  354. }
  355. }
  356. ?>