PageRenderTime 60ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/simpletest/webapp/libphp/Properties.php

https://github.com/gatewaytestcase/NUCRI-Science-Gateway-Test-Case
PHP | 1091 lines | 568 code | 124 blank | 399 comment | 110 complexity | 90e0d3372190a51840536ca9d57b3451 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0
  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | Near implementation of the java.util.Properties API for PHP 5 |
  4. // | Copyright (C) 2005 Craig Manley |
  5. // +----------------------------------------------------------------------+
  6. // | This library is free software; you can redistribute it and/or modify |
  7. // | it under the terms of the GNU Lesser General Public License as |
  8. // | published by the Free Software Foundation; either version 2.1 of the |
  9. // | License, or (at your option) any later version. |
  10. // | |
  11. // | This library is distributed in the hope that it will be useful, but |
  12. // | WITHOUT ANY WARRANTY; without even the implied warranty of |
  13. // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
  14. // | Lesser General Public License for more details. |
  15. // | |
  16. // | You should have received a copy of the GNU Lesser General Public |
  17. // | License along with this library; if not, write to the Free Software |
  18. // | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
  19. // | USA |
  20. // | |
  21. // | LGPL license URL: http://opensource.org/licenses/lgpl-license.php |
  22. // +----------------------------------------------------------------------+
  23. // | References: |
  24. // | 1. The Perl (CPAN) module Config::Properties 0.58, originally |
  25. // | developed by Randy Jay Yarger, later mantained by me, and currently |
  26. // | mantained by Salvador Fandi�o. |
  27. // | See: http://search.cpan.org/search?query=Config%3A%3AProperties |
  28. // | 2. The Java docs for the java.util.Properties API which can be found |
  29. // | at http://java.sun.com/j2se/1.3/docs/api/index.html . |
  30. // +----------------------------------------------------------------------+
  31. // | Author: Craig Manley |
  32. // +----------------------------------------------------------------------+
  33. //
  34. // $Id: Properties.php,v 1.1 2005/11/27 16:39:23 cmanley Exp $
  35. /**
  36. * @file
  37. * Contains the Properties class and it's private classes.
  38. */
  39. /**
  40. * @mainpage
  41. * @author Craig Manley
  42. * @since PHP 5.0
  43. * @date $Date: 2005/11/27 16:39:23 $
  44. * @version $Revision: 1.1 $
  45. *
  46. * This is a near implementation of the java.util.Properties API for PHP 5.
  47. * The Properties class has an interface that is very similar to it's Java counterpart.
  48. * One notable difference however, is that this class is capable of retaining the
  49. * original structure, including comments and blanks, of a loaded properties file when
  50. * saving the properties to a new file, which is something that it's Java counterpart doesn't do.
  51. *
  52. * @section motivation MOTIVATION:
  53. * Since I develop (web) applications that sometimes consist of code written in different
  54. * programming languages, I needed a standard configuration file format and API's in each
  55. * of the programming languages to parse shared configuration files.
  56. *
  57. * Java has a java.util.Properties class which uses a standard configuration file format
  58. * that looks a lot like most .conf files in UNIX (of which there is no real standard format).
  59. *
  60. * Perl has the lean and mean Config::Properties class which I often use to parse
  61. * (and sometimes write) Java properties files.
  62. *
  63. * PHP however didn't have something similar.
  64. *
  65. * @section file_format FILE FORMAT:
  66. * The format of a Java-style property file is that of a key-value pair seperated
  67. * by either whitespace, the colon (:) character, or the equals (=) character.
  68. * Whitespace before the key and on either side of the seperator is ignored.
  69. *
  70. * Lines that begin with either a hash (#) or a bang (!) are considered comment
  71. * lines and ignored.
  72. *
  73. * A backslash (\) at the end of a line signifies a continuation and the next
  74. * line is counted as part of the current line (minus the backslash, any whitespace
  75. * after the backslash, the line break, and any whitespace at the beginning of the next line).
  76. *
  77. * The official references used to determine this format can be found in the Java API docs
  78. * for java.util.Properties at http://java.sun.com/j2se/1.3/docs/api/java/util/Properties.html .
  79. *
  80. * @section synopsis SYNOPSIS:
  81. *
  82. * @code
  83. * my $p = new Properties();
  84. *
  85. * // Read from string (from array and file handle is possible too):
  86. * $p->load(file_get_contents('site.properties');
  87. *
  88. * // Get associative array of all loaded properties:
  89. * $config = $p->toArray();
  90. *
  91. * // Get a single property's value:
  92. * $email_from = $p->getProperty('email.from');
  93. *
  94. * // Set a property:
  95. * $p->setProperty('email.from', 'no-reply@server.com');
  96. *
  97. * // Remove a property:
  98. * $p->remove('email.from');
  99. *
  100. * // Save properties method 1:
  101. * $p->store('site.properties');
  102. *
  103. * // Save properties method 2:
  104. * file_put_contents('site.properties', $p->toString());
  105. *
  106. * // Save properties method 3 (includes comments and blanks):
  107. * file_put_contents('site.properties', $p->toString(true));
  108. * @endcode
  109. *
  110. * Copyright � 2005, Craig Manley.
  111. *
  112. */
  113. /**
  114. * @ignore Require the section classes.
  115. */
  116. //require_once(realpath(dirname(__FILE__) . '/Section/Blank.php'));
  117. //require_once(realpath(dirname(__FILE__) . '/Section/Comment.php'));
  118. //require_once(realpath(dirname(__FILE__) . '/Section/Property.php'));
  119. /**
  120. * @ignore Create "Standard PHP Library" compatible exception classes if the SPL extension isn't available.
  121. * @ignore The SPL extension exists in PHP 5.0, but is only installed by default since PHP 5.1.
  122. */
  123. //if (!extension_loaded('SPL')) {
  124. if (!class_exists('LogicException')) {
  125. eval('class LogicException extends Exception {};');
  126. }
  127. if (!class_exists('InvalidArgumentException')) {
  128. eval('class InvalidArgumentException extends LogicException {};');
  129. }
  130. //}
  131. /**
  132. * Near implementation of the java.util.Properties class.
  133. * This is the class that you need to use for parsing and writing Java style property files.
  134. * It makes use of exception classes from the "Standard PHP Library" extension (http://www.php.net/spl)
  135. * that is installed per default since PHP 5.1, however it is not required to use this class.
  136. */
  137. class Properties {
  138. protected $defaults = null; // Properties object to use for defaults.
  139. private $sections = array(); // array of Properties_Section objects loaded from a properties file with the load method.
  140. private $properties = array(); // associative array of key => Properties_Section_Property object references.
  141. const WHITE_SPACE_CHARS = " \t\r\n\x0C"; // 0x0c == \f (form feed)
  142. /**
  143. * Constructor. Creates an empty property list.
  144. *
  145. * @param defaults optional Properties object to use for defaults.
  146. * @throw InvalidArgumentException
  147. */
  148. //public function __construct(Properties $defaults = null) { // Type hints with null as default only work in PHP5.1 up.
  149. public function __construct($defaults = null) { // This is for PHP 5.0 up.
  150. if (isset($defaults)) {
  151. if (!($defaults instanceof Properties)) {
  152. throw new InvalidArgumentException('The $defaults parameter must be null or an instance of ' . __CLASS__ . '.');
  153. }
  154. $this->defaults = $defaults;
  155. }
  156. }
  157. /**
  158. * Tests an unescaped key's syntax. Throws an exception on error.
  159. *
  160. * @param key
  161. * @throw InvalidArgumentException if key syntax is invalid.
  162. */
  163. protected static function _testKey($key) {
  164. if (!(isset($key) && strlen($key))) {
  165. throw new InvalidArgumentException('The $key parameter must be a string of 1 or more characters.');
  166. }
  167. }
  168. /**
  169. * Sets a property.
  170. *
  171. * @param key
  172. * @param value
  173. * @throw InvalidArgumentException
  174. */
  175. public function setProperty($key, $value) {
  176. Properties_Section_Property::testKey($key);
  177. Properties_Section_Property::testValue($key);
  178. if (array_key_exists($key, $this->properties)) {
  179. $this->properties[$key]->setValue($value);
  180. }
  181. else {
  182. $p = new Properties_Section_Property($key, $value);
  183. array_push($this->sections, $p);
  184. $this->properties[$key] = $p;
  185. }
  186. }
  187. /**
  188. * Does what Perl's chomp() function does.
  189. *
  190. * @param string reference
  191. * @return integer
  192. */
  193. private static function _chomp(&$string) {
  194. $result = 0;
  195. if (is_array($string)) {
  196. foreach($string as $i => $val) {
  197. $result += self::_chomp($string[$i]);
  198. }
  199. }
  200. else {
  201. while (strlen($string)) {
  202. $endchar = substr($string, -1);
  203. if (($endchar === "\n") || ($endchar === "\r")) {
  204. $string = substr($string, 0, -1);
  205. $result++;
  206. }
  207. else {
  208. break;
  209. }
  210. }
  211. // // alternative:
  212. // if (preg_match('/([\\r|\\n]+)$/', $string, $matches)) {
  213. // $result = strlen($matches[1]);
  214. // $string = substr($string, 0, -$result);
  215. // }
  216. }
  217. return $result;
  218. }
  219. /**
  220. * Determines if the given line ends with an unescaped continuation character (the '\').
  221. * If the result is true, then the continuation character is also stripped off the end of the line too.
  222. *
  223. * @param line string reference
  224. * @return boolean
  225. */
  226. private static function _continueLine(&$line) {
  227. $count = 0;
  228. $len = strlen($line);
  229. for ($i = $len - 1; $i >= 0; $i--) {
  230. if (substr($line, $i, 1) == '\\') {
  231. $count++;
  232. }
  233. else {
  234. break;
  235. }
  236. }
  237. $result = (boolean) $count % 2;
  238. if ($result) {
  239. $line = substr($line, 0, $len - 1);
  240. }
  241. return $result;
  242. }
  243. /**
  244. * Loads properties from a file handle or string.
  245. * You can pass either an file handle open for reading,
  246. * an array of lines, or a string buffer containing all the lines.
  247. * When a file handle is passed, the caller is responsible for opening and closing it.
  248. *
  249. * @param source - an open file handle, string buffer, or array of lines.
  250. * @throw InvalidArgumentException
  251. */
  252. public function load($source) {
  253. if (!isset($source)) {
  254. throw new InvalidArgumentException('The $source parameter may not be null.');
  255. }
  256. $lines = null;
  257. if (is_array($source)) {
  258. $lines = $source;
  259. }
  260. elseif (is_string($source)) {
  261. $lines = preg_split('/(\\r\\n|\\n|\\r)/', $source); // DOS: \r\n UNIX: \n MAC: \r
  262. }
  263. elseif (get_resource_type($source) === 'file') {
  264. $contents = '';
  265. while (!feof($source)) {
  266. $contents .= fread($source, 8192);
  267. }
  268. $lines = preg_split('/(\\r\\n|\\n|\\r)/', $source); // DOS: \r\n UNIX: \n MAC: \r
  269. }
  270. else {
  271. throw new InvalidArgumentException('The $source parameter of type "' . gettype($source) . '" is not supported.');
  272. }
  273. // Now process the lines
  274. for ($i = 0; $i < count($lines); $i++) {
  275. $line = ltrim($lines[$i], self::WHITE_SPACE_CHARS);
  276. self::_chomp($line);
  277. // handle blanks
  278. if (strlen($line) == 0) {
  279. if ($i < count($lines) -1) { // don't store last blank.
  280. $p = new Properties_Section_Blank();
  281. array_push($this->sections, $p);
  282. }
  283. }
  284. // handle comments
  285. elseif (preg_match('/^(#|!)(\\s*)(.*)$/', $line, $matches)) {
  286. $p = new Properties_Section_Comment($matches[3], $matches[1], $matches[2]);
  287. array_push($this->sections, $p);
  288. }
  289. // handle properties or multiline sections.
  290. else {
  291. // handle continuation lines
  292. while (self::_continueLine($line) && (++$i < count($lines))) {
  293. self::_chomp($lines[$i]);
  294. $line .= ltrim($lines[$i], self::WHITE_SPACE_CHARS);
  295. }
  296. // handle blanks (in case there were multiple lines with only a continuation character on them).
  297. if (strlen($line) == 0) {
  298. if ($i < count($lines) -1) { // don't store last blank.
  299. $p = new Properties_Section_Blank();
  300. array_push($this->sections, $p);
  301. }
  302. }
  303. // handle comments (in case there were multiple lines with only a continuation character on them followed by a comment).
  304. elseif (preg_match('/^(#|!)(\\s*)(.*)$/', $line, $matches)) {
  305. $p = new Properties_Section_Comment($matches[3], $matches[1], $matches[2]);
  306. array_push($this->sections, $p);
  307. }
  308. // handle properties
  309. else {
  310. $p = new Properties_Section_Property($line);
  311. $key = $p->getKey();
  312. if (array_key_exists($key, $this->properties)) {
  313. // Property name already exists.
  314. array_push($this->sections, new Properties_Section_Comment("WARNING: The following previously encountered property was commented out:\n$line"));
  315. }
  316. else {
  317. $this->properties[$key] = $p;
  318. array_push($this->sections, $p);
  319. }
  320. }
  321. }
  322. }
  323. }
  324. /**
  325. * Writes the property list (key and element pairs) to the given file name or handle.
  326. * If you want to retain the original structure of the file(s) you loaded as much as possible,
  327. * then use toString(true) method instead.
  328. *
  329. * @param file file name or file handle opened in write or append mode.
  330. * @param header optional header line which will be written as a comment.
  331. * @return array
  332. * @throw InvalidArgumentException
  333. */
  334. public function store($file, $header = null) {
  335. $data = '';
  336. // header comment (supports multiline headers too unlike the java method).
  337. if (isset($header) && strlen($header)) {
  338. $comment = new Properties_Section_Comment($header, false);
  339. $data .= $comment->toString();
  340. }
  341. // date comment, e.g. #Sat Nov 26 16:29:36 CET 2005
  342. $data .= '#' . date('r') . "\n";
  343. $data .= $this->toString();
  344. // write
  345. if (get_resource_type($file) === 'file') {
  346. fwrite($file, $data);
  347. }
  348. else {
  349. file_put_contents($file, $data);
  350. }
  351. }
  352. /**
  353. * Gets a property value.
  354. * Returns a string (possibly empty), or null if not found.
  355. *
  356. * @param key
  357. * @param defaultValue optional default value to return if no match is found.
  358. * @return string or null.
  359. * @throw InvalidArgumentException
  360. */
  361. public function getProperty($key, $defaultValue = null) {
  362. self::_testKey($key);
  363. if (array_key_exists($key, $this->properties)) {
  364. return $this->properties[$key]->getValue();
  365. }
  366. if (isset($this->defaults)) {
  367. return $this->defaults->getProperty($key, $defaultValue);
  368. }
  369. return $defaultValue;
  370. }
  371. /**
  372. * Returns an array of all property names.
  373. *
  374. * @return array
  375. */
  376. public function propertyNames() {
  377. return array_keys($this->properties);
  378. }
  379. /**
  380. * Returns all the key -> value pairs as an associative array.
  381. *
  382. * @return array
  383. */
  384. public function toArray() {
  385. $result = array();
  386. foreach ($this->properties as $key => $section) {
  387. $result[$key] = $section->getValue();
  388. }
  389. return $result;
  390. }
  391. /**
  392. * Returns a string representation of the properties, without all the blanks and comments.
  393. *
  394. * @param everything optional boolean, default false. If true then comments and blanks will be included.
  395. * @return string
  396. */
  397. public function toString($everything = false) {
  398. $result = '';
  399. if ($everything) {
  400. foreach ($this->sections as $section) {
  401. $result .= $section->toString();
  402. }
  403. }
  404. else {
  405. foreach (array_values($this->properties) as $section) {
  406. $result .= $section->toString();
  407. }
  408. }
  409. return $result;
  410. }
  411. /**
  412. * Determines if this object contains the exact same property name and value pairs as the given object.
  413. * Part of the java.util.Hashtable API.
  414. *
  415. * @param other Properties object.
  416. * @return boolean
  417. */
  418. public function equals(Properties $other) {
  419. $other_names = $other->propertyNames();
  420. $my_names = $this->propertyNames();
  421. if ((count($other_names) != count($my_names)) || count(array_diff($other_names, $my_names))) {
  422. return false;
  423. }
  424. for ($i = 0; $i < count($my_names); $i++) {
  425. if ($this->getProperty($my_names[$i]) !== $other->getProperty($my_names[$i])) {
  426. return false;
  427. }
  428. }
  429. return true;
  430. }
  431. /**
  432. * Removes all properties.
  433. * Part of the java.util.Hashtable API.
  434. */
  435. public function clear() {
  436. $this->properties = array();
  437. $this->sections = array();
  438. }
  439. /**
  440. * Deletes a property.
  441. * Part of the java.util.Hashtable API.
  442. * Returns the property's last known value as a string (possibly empty), or null if not found.
  443. *
  444. * @param key
  445. * @return string or null
  446. */
  447. public function remove($key) {
  448. self::_testKey($key);
  449. $result = null;
  450. if (array_key_exists($key, $this->properties)) {
  451. // Remove the section and any blank or comment lines directly before it.
  452. $section_removed = false;
  453. for ($i = 0; $i < count($this->sections); $i++) {
  454. if (($this->sections[$i] instanceof Properties_Section_Property) && ($this->sections[$i]->getKey() == $key)) {
  455. $splice_from = $splice_to = $i;
  456. if ($i > 0) {
  457. $previous_section_class = get_class($this->sections[$i - 1]);
  458. if ($previous_section_class != 'Properties_Section_Property') {
  459. for ($j = $i - 1; $j >= 0; $j--) {
  460. if (get_class($this->sections[$j]) == $previous_section_class) {
  461. $splice_from = $j;
  462. }
  463. else {
  464. break;
  465. }
  466. }
  467. }
  468. }
  469. array_splice($this->sections, $splice_from, 1 + $splice_to - $splice_from);
  470. $section_removed = true;
  471. break;
  472. }
  473. }
  474. if (!$section_removed) {
  475. throw new LogicException("Cannot find (and therefore remove) the section with key '$key'.");
  476. }
  477. // remove the property
  478. unset($this->properties[$key]);
  479. }
  480. return $result;
  481. }
  482. }
  483. /****************************** End of class Properties ******************************/
  484. /**
  485. * Internal Section interface that you don't need to know about.
  486. */
  487. interface Properties_ISection {
  488. /**
  489. * The implementation of this method must return the string representation of the section.
  490. *
  491. * @return string
  492. */
  493. public function toString();
  494. }
  495. /****************************** End of interface Properties_ISection ******************************/
  496. /**
  497. * Internal Blank section class that you don't need to know about.
  498. * Lines that contain nothing or only whitespace are considered blanks.
  499. */
  500. class Properties_Section_Blank implements Properties_ISection {
  501. /**
  502. * Returns the string representation of the section.
  503. *
  504. * @return string
  505. */
  506. public function toString() {
  507. return "\n";
  508. }
  509. }
  510. /****************************** End of class Properties_Section_Blank ******************************/
  511. /**
  512. * Internal Comment section class that you don't need to know about.
  513. *
  514. * A comment line is a line whose first non-whitespace character
  515. * is an ASCII # or ! is ignored (thus, # (hash) or ! (bang) indicate comment lines).
  516. */
  517. class Properties_Section_Comment implements Properties_ISection {
  518. private $comment_char = null;
  519. private $padding = null;
  520. private $value = null;
  521. /**
  522. * Constructor.
  523. *
  524. * @param value string
  525. * @param comment_char optional comment character, default '#' (allowed values are '#' or !').
  526. * @param padding optional comment padding string, default ' '.
  527. * @throw InvalidArgumentException
  528. */
  529. public function __construct($value, $comment_char = '#', $padding = ' ') {
  530. $this->value = $value;
  531. $this->_testCommentChar($comment_char);
  532. $this->comment_char = $comment_char;
  533. $this->_testPadding($padding);
  534. $this->padding = $padding;
  535. }
  536. /**
  537. * Tests the syntax of a comment character.
  538. *
  539. * @param $char
  540. * @throw InvalidArgumentException
  541. */
  542. protected static function _testCommentChar($char) {
  543. if (!isset($char)) {
  544. throw new InvalidArgumentException('Comment character may not be null.');
  545. }
  546. if (($char != '#') && ($char != '!')) {
  547. throw new InvalidArgumentException('String "' . $char . '" is not a valid comment character.');
  548. }
  549. }
  550. /**
  551. * Tests the syntax of a padding string.
  552. *
  553. * @param string
  554. * @throw InvalidArgumentException
  555. */
  556. protected static function _testPadding($string) {
  557. if (!isset($string)) {
  558. throw new InvalidArgumentException("Padding may not be NULL!");
  559. }
  560. if (!preg_match('/^( |\\t|#|!)*$/', $string)) {
  561. throw new InvalidArgumentException('Padding string contains invalid characters.');
  562. }
  563. }
  564. /**
  565. * Returns the string representation of the section.
  566. *
  567. * @return string
  568. */
  569. public function toString() {
  570. $lines = preg_split('/\\r?\\n\\r?/', $this->value);
  571. return $this->comment_char . $this->padding . implode("\n" . $this->comment_char . $this->padding, $lines) . "\n";
  572. }
  573. /**
  574. * Sets the value. This is the complete comment, but without the initial comment character and padding.
  575. *
  576. * @param value string
  577. */
  578. public function setValue($value) {
  579. $this->value = $value;
  580. }
  581. /**
  582. * Returns the value. This is the complete comment, but without the comment character and padding.
  583. *
  584. * @return string
  585. */
  586. public function getValue() {
  587. return $this->value;
  588. }
  589. }
  590. /****************************** End of class Properties_Section_Comment ******************************/
  591. /**
  592. * Internal Property section class that you don't need to know about.
  593. *
  594. * Everything in a properties file besides comments and blank lines, are considered as (name-value) properties.
  595. */
  596. class Properties_Section_Property implements Properties_ISection {
  597. private $key = null;
  598. private $value = null;
  599. private $seperator = null;
  600. const SEPERATOR_REGEX_PATTERN = '[\\t \\x0c]*[=:\\t \\x0c][\\t \\x0c]*';
  601. /**
  602. * Overloaded constructor.
  603. *
  604. * Overload 1:
  605. * @param line a raw property line.
  606. *
  607. * Overload 2:
  608. * @param key
  609. * @param value
  610. * @param seperator optional seperator character (default '=').
  611. * @throw InvalidArgumentException
  612. */
  613. public function __construct() {
  614. $key = null;
  615. $value = null;
  616. $seperator = null;
  617. switch (func_num_args()) {
  618. case 1:
  619. $line = func_get_arg(0);
  620. if (isset($line)) {
  621. $line = ltrim($line, Properties::WHITE_SPACE_CHARS);
  622. }
  623. if (!(isset($line) && strlen($line))) {
  624. throw new InvalidArgumentException('Empty line passed into overloaded constructor.');
  625. }
  626. $parts = $this->_parseLine($line);
  627. if (!$parts) {
  628. throw new InvalidArgumentException('Invalid property line passed into overloaded constructor.');
  629. }
  630. list($key, $seperator, $value) = $parts;
  631. break;
  632. case 2:
  633. case 3:
  634. $key = func_get_arg(0);
  635. $value = func_get_arg(1);
  636. if (func_num_args() == 3) {
  637. $seperator = func_get_arg(2);
  638. }
  639. if (!isset($seperator)) {
  640. $seperator = '=';
  641. }
  642. break;
  643. default:
  644. throw new InvalidArgumentException('Invalid arguments passed into overloaded constructor.');
  645. }
  646. $this->_testKey($key);
  647. $this->_testValue($value);
  648. $this->_testSeperator($seperator);
  649. $this->key = $key;
  650. $this->seperator = $seperator;
  651. $this->value = $value;
  652. }
  653. /**
  654. * Splits a raw key-value line into it's constituent parts.
  655. * Returns an array with the 3 elements: key, seperator, value
  656. *
  657. * @param line reference
  658. * @return array
  659. */
  660. private static function _parseLine(&$line) {
  661. // Locate unescaped seperator.
  662. // Seperators match this sequence and may not be prefixed with an escape character: /\s*(=|:)\s*/
  663. $result = false;
  664. $key = $seperator = $value = null;
  665. $offset = 0;
  666. while (preg_match('/^((?U).+)(' . self::SEPERATOR_REGEX_PATTERN . ')(.*)$/s', substr($line, $offset), $matches, PREG_OFFSET_CAPTURE)) { // (?U) means ungreedy
  667. $possible_key = $offset ? substr($line, 0, $offset) . $matches[1][0] : $matches[1][0];
  668. $count = 0;
  669. $len = strlen($possible_key);
  670. for ($i = $len - 1; $i >= 0; $i--) {
  671. if (substr($possible_key, $i, 1) == '\\') {
  672. $count++;
  673. }
  674. else {
  675. break;
  676. }
  677. }
  678. if ($count % 2 == 0) { // even number of direct prior escapes is ok
  679. $key = $possible_key;
  680. $seperator = $matches[2][0];
  681. $value = $matches[3][0];
  682. break;
  683. }
  684. $offset += $matches[2][1];
  685. }
  686. if (!isset($key)) {
  687. $key = $line;
  688. $seperator = '=';
  689. }
  690. if (!isset($value)) {
  691. $value = '';
  692. }
  693. return array(self::unescape($key), $seperator, self::unescape($value));
  694. }
  695. /**
  696. * Tests an unescaped key's syntax. Throws an exception on error.
  697. *
  698. * @param key
  699. * @throw InvalidArgumentException
  700. */
  701. protected static function _testKey($key) {
  702. if (!isset($key)) {
  703. throw new InvalidArgumentException("Property key may not be NULL!");
  704. }
  705. if (!strlen($key)) {
  706. throw new InvalidArgumentException("Property key may not be empty!");
  707. }
  708. }
  709. /**
  710. * Tests an unescaped value's syntax. Throws an exception on error.
  711. *
  712. * @param value
  713. * @throw InvalidArgumentException
  714. */
  715. protected static function _testValue($value) {
  716. if (!isset($value)) {
  717. throw new InvalidArgumentException("Property value may not be NULL!");
  718. }
  719. }
  720. /**
  721. * Tests a seperator's syntax. Throws an exception on error.
  722. *
  723. * @param seperator
  724. * @throw InvalidArgumentException
  725. */
  726. protected static function _testSeperator($seperator) {
  727. if (!isset($seperator)) {
  728. throw new InvalidArgumentException("Seperator may not be NULL!");
  729. }
  730. if (!strlen($seperator)) {
  731. throw new InvalidArgumentException("Seperator may not be empty!");
  732. }
  733. if (!preg_match('/^' . self::SEPERATOR_REGEX_PATTERN . '$/', $seperator)) {
  734. throw new InvalidArgumentException("Bad syntax in seperator string!");
  735. }
  736. }
  737. /**
  738. * Escapes a character.
  739. * The characters LF (line feed), CR (carriage return), FF (form feed), : (colon), # (hash), ! (bang),
  740. * = (equals), tab, and backslash are escaped C-style with a backslash character.
  741. * Characters outside the range 0x20 to 0x7E are encoded in the \\uxxxx format.
  742. * If $escape_space is true then the space character is escaped too.
  743. *
  744. * @param char character
  745. * @param escape_space boolean
  746. * @return string
  747. */
  748. protected static function _escapeChar($char, $escape_space) {
  749. static $escmap = null;
  750. if (is_null($escmap)) {
  751. $escmap = array("\r" => 'r',
  752. "\n" => 'n',
  753. chr(0x0c) => 'f',
  754. "\t" => 't',
  755. '\\' => '\\',
  756. ':' => ':',
  757. '#' => '#',
  758. '!' => '!',
  759. '=' => '=');
  760. }
  761. if (array_key_exists($char, $escmap)) {
  762. return '\\' . $escmap[$char];
  763. }
  764. elseif ($escape_space && ($char == ' ')) {
  765. return '\\ ';
  766. }
  767. elseif ((ord($char) < 0x20) || (ord($char) > 0x7e)) {
  768. return sprintf('\\u%04X', ord($char));
  769. }
  770. return $char;
  771. }
  772. /**
  773. * Escapes a string.
  774. * The characters LF (line feed), CR (carriage return), FF (form feed), : (colon), # (hash), ! (bang),
  775. * = (equals), tab, and backslash are escaped C-style with a backslash character.
  776. * Characters outside the range 0x20 to 0x7E are encoded in the \\uxxxx format.
  777. * If $escapeSpace is true then the ' ' characters are escaped too.
  778. *
  779. * @param s
  780. * @param escape_space boolean
  781. * @return string
  782. */
  783. protected static function escape($s, $escape_space) {
  784. $result = preg_replace('/([\\s=:#!\\\\]|[^\\x20-\\x7e])/e', 'self::_escapeChar("\\1", $escape_space)', $s);
  785. // If the first character of a string is a space then always escape it, even if $escape_space is false.
  786. if (!$escape_space && strlen($result)) {
  787. if (substr($result,0,1) === ' ') {
  788. $result = "\\$result";
  789. }
  790. }
  791. return $result;
  792. }
  793. /**
  794. * Escapes a key.
  795. * The characters space, LF (line feed), CR (carriage return), FF (form feed), : (colon), # (hash), ! (bang),
  796. * = (equals), tab, and backslash are escaped C-style with a backslash character.
  797. * Characters outside the range 0x20 to 0x7E are encoded in the \\uxxxx format.
  798. *
  799. * @param key
  800. * @return string
  801. */
  802. public static function escapeKey($key) {
  803. return self::escape($key, true);
  804. }
  805. /**
  806. * Escapes a value.
  807. * The characters LF (line feed), CR (carriage return), FF (form feed), : (colon), # (hash), ! (bang),
  808. * = (equals), tab, and backslash are escaped C-style with a backslash character.
  809. * Characters outside the range 0x20 to 0x7E are encoded in the \\uxxxx format.
  810. * If the value starts with a whitespace character, then that too is escaped.
  811. *
  812. * @param value
  813. * @return string
  814. */
  815. public static function escapeValue($value) {
  816. return self::escape($value, false);
  817. }
  818. /**
  819. * Unescapes an escaped character sequence.
  820. *
  821. * @param s string, or array of matches from preg_replace_callback().
  822. * @return string
  823. */
  824. protected static function _unescapeChar($s) {
  825. if (is_array($s)) {
  826. $s = $s[0];
  827. }
  828. if (substr($s,0,1) != '\\') {
  829. return $s; // it wasn't escaped.
  830. }
  831. $s = substr($s,1); // drop \ character
  832. if (strlen($s) > 1) {
  833. if (!preg_match('/^u[\da-fA-F]{4}$/', $s)) {
  834. throw new Exception("Malformed \\uxxxx encoding in '\\$s'.");
  835. }
  836. $ord = hexdec(substr($s,1));
  837. if ($ord < 128) {
  838. return chr($ord);
  839. }
  840. else if ($ord < 2048) {
  841. return (chr(192 + (($ord - ($ord % 64)) / 64))) .
  842. (chr(128 + ($ord % 64)));
  843. }
  844. else {
  845. return (chr(224 + (($ord - ($ord % 4096)) / 4096))) .
  846. (chr(128 + ((($ord % 4096) - ($ord % 64)) / 64))) .
  847. (chr(128 + ($ord % 64)));
  848. }
  849. }
  850. static $unescmap = null;
  851. if (is_null($unescmap)) {
  852. $unescmap = array('r' => "\r",
  853. 'n' => "\n",
  854. 'f' => chr(0x0c),
  855. 't' => "\t");
  856. }
  857. if (array_key_exists($s, $unescmap)) {
  858. return $unescmap[$s];
  859. }
  860. return $s;
  861. }
  862. /**
  863. * Unescapes a string.
  864. *
  865. * @param s
  866. * @return string
  867. */
  868. public static function unescape($s) {
  869. return preg_replace_callback('/(\\\\u[\da-fA-F]{4}|\\\\(.))/', array(__CLASS__, '_unescapeChar'), $s);
  870. }
  871. /**
  872. * Returns the string representation of the section.
  873. *
  874. * @param max_chars_per_line unsigned integer, maximum number of characters per line (default 120).
  875. * @return string
  876. */
  877. public function toString($max_chars_per_line = 120) {
  878. if (isset($max_chars_per_line)) {
  879. if ($max_chars_per_line < 1) {
  880. $max_chars_per_line = null;
  881. }
  882. }
  883. $key_and_sep = $this->escapeKey($this->key) . $this->seperator;
  884. $value = $this->escapeValue($this->value);
  885. if (!$max_chars_per_line || (strlen($key_and_sep) + strlen($value) <= $max_chars_per_line)) {
  886. return "$key_and_sep$value\n";
  887. }
  888. $lines = array();
  889. if (strlen($key_and_sep) > $max_chars_per_line / 2) { // key and seperator are quite long so leave them on their own line
  890. array_push($lines, $key_and_sep);
  891. }
  892. elseif (preg_match('/^.{1,' . ($max_chars_per_line - strlen($key_and_sep)) . '}(?<!\\\\)[ \\t]/', $value, $matches)) { // if first breakable part of value is short then place it on same line as key
  893. if (strlen($key_and_sep) < 8) { // common tab length
  894. array_push($lines, $key_and_sep . "\t" . $matches[0]);
  895. }
  896. else {
  897. array_push($lines, $key_and_sep . $matches[0]);
  898. }
  899. $value = substr($value, strlen($matches[0]));
  900. }
  901. else {
  902. array_push($lines, $key_and_sep); // because value seems too unbreakable to put on same line as key.
  903. }
  904. // split out the (rest of) value
  905. $parts = preg_split('/(.{1,' . $max_chars_per_line . '}(?<!\\\\)[ \\t])/', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
  906. for ($i = 0; $i < count($parts); $i += 2) {
  907. $len = strlen($parts[$i]); // usually empty, unless unsplitable.
  908. if ($len > $max_chars_per_line) {
  909. $line = '';
  910. for ($j = 0; $j < $len; $j += $max_chars_per_line) {
  911. $line .= substr($parts[$i], $j, $max_chars_per_line);
  912. if ($j + $max_chars_per_line < $len - 1) {
  913. $line .= "\\\n\t";
  914. }
  915. }
  916. }
  917. else {
  918. $line = $parts[$i];
  919. }
  920. if ($i+1 < count($parts)) {
  921. if (strlen($line)) {
  922. $line .= "\\\n\t";
  923. }
  924. $line .= $parts[$i+1]; // the preg_split delimiter
  925. }
  926. array_push($lines, $line);
  927. }
  928. return implode("\\\n\t", $lines) . "\n";
  929. }
  930. /**
  931. * Sets the value.
  932. *
  933. * @param value
  934. */
  935. public function setValue($value) {
  936. if ((string)$value != $this->value) {
  937. $this->testValue($value);
  938. $this->value = (string) $value;
  939. }
  940. }
  941. /**
  942. * Returns the key.
  943. *
  944. * @return string
  945. */
  946. public function getKey() {
  947. return $this->key;
  948. }
  949. /**
  950. * Returns the value.
  951. *
  952. * @return string
  953. */
  954. public function getValue() {
  955. return $this->value;
  956. }
  957. }
  958. /****************************** End of class Properties_Section_Property ******************************/
  959. ?>