shell.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. <?php
  2. /**
  3. * Class used internally by Diff to actually compute the diffs.
  4. *
  5. * This class uses the Unix `diff` program via shell_exec to compute the
  6. * differences between the two input arrays.
  7. *
  8. * $Horde: framework/Text_Diff/Diff/Engine/shell.php,v 1.6.2.4 2009/01/06 15:23:41 jan Exp $
  9. *
  10. * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
  11. *
  12. * See the enclosed file COPYING for license information (LGPL). If you did
  13. * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
  14. *
  15. * @author Milian Wolff <mail@milianw.de>
  16. * @package Text_Diff
  17. * @since 0.3.0
  18. */
  19. class Text_Diff_Engine_shell {
  20. /**
  21. * Path to the diff executable
  22. *
  23. * @var string
  24. */
  25. var $_diffCommand = 'diff';
  26. /**
  27. * Returns the array of differences.
  28. *
  29. * @param array $from_lines lines of text from old file
  30. * @param array $to_lines lines of text from new file
  31. *
  32. * @return array all changes made (array with Text_Diff_Op_* objects)
  33. */
  34. function diff($from_lines, $to_lines)
  35. {
  36. array_walk($from_lines, array('Text_Diff', 'trimNewlines'));
  37. array_walk($to_lines, array('Text_Diff', 'trimNewlines'));
  38. $temp_dir = Text_Diff::_getTempDir();
  39. // Execute gnu diff or similar to get a standard diff file.
  40. $from_file = tempnam($temp_dir, 'Text_Diff');
  41. $to_file = tempnam($temp_dir, 'Text_Diff');
  42. $fp = fopen($from_file, 'w');
  43. fwrite($fp, implode("\n", $from_lines));
  44. fclose($fp);
  45. $fp = fopen($to_file, 'w');
  46. fwrite($fp, implode("\n", $to_lines));
  47. fclose($fp);
  48. $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file);
  49. unlink($from_file);
  50. unlink($to_file);
  51. if (is_null($diff)) {
  52. // No changes were made
  53. return array(new Text_Diff_Op_copy($from_lines));
  54. }
  55. $from_line_no = 1;
  56. $to_line_no = 1;
  57. $edits = array();
  58. // Get changed lines by parsing something like:
  59. // 0a1,2
  60. // 1,2c4,6
  61. // 1,5d6
  62. preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff,
  63. $matches, PREG_SET_ORDER);
  64. foreach ($matches as $match) {
  65. if (!isset($match[5])) {
  66. // This paren is not set every time (see regex).
  67. $match[5] = false;
  68. }
  69. if ($match[3] == 'a') {
  70. $from_line_no--;
  71. }
  72. if ($match[3] == 'd') {
  73. $to_line_no--;
  74. }
  75. if ($from_line_no < $match[1] || $to_line_no < $match[4]) {
  76. // copied lines
  77. assert('$match[1] - $from_line_no == $match[4] - $to_line_no');
  78. array_push($edits,
  79. new Text_Diff_Op_copy(
  80. $this->_getLines($from_lines, $from_line_no, $match[1] - 1),
  81. $this->_getLines($to_lines, $to_line_no, $match[4] - 1)));
  82. }
  83. switch ($match[3]) {
  84. case 'd':
  85. // deleted lines
  86. array_push($edits,
  87. new Text_Diff_Op_delete(
  88. $this->_getLines($from_lines, $from_line_no, $match[2])));
  89. $to_line_no++;
  90. break;
  91. case 'c':
  92. // changed lines
  93. array_push($edits,
  94. new Text_Diff_Op_change(
  95. $this->_getLines($from_lines, $from_line_no, $match[2]),
  96. $this->_getLines($to_lines, $to_line_no, $match[5])));
  97. break;
  98. case 'a':
  99. // added lines
  100. array_push($edits,
  101. new Text_Diff_Op_add(
  102. $this->_getLines($to_lines, $to_line_no, $match[5])));
  103. $from_line_no++;
  104. break;
  105. }
  106. }
  107. if (!empty($from_lines)) {
  108. // Some lines might still be pending. Add them as copied
  109. array_push($edits,
  110. new Text_Diff_Op_copy(
  111. $this->_getLines($from_lines, $from_line_no,
  112. $from_line_no + count($from_lines) - 1),
  113. $this->_getLines($to_lines, $to_line_no,
  114. $to_line_no + count($to_lines) - 1)));
  115. }
  116. return $edits;
  117. }
  118. /**
  119. * Get lines from either the old or new text
  120. *
  121. * @access private
  122. *
  123. * @param array &$text_lines Either $from_lines or $to_lines
  124. * @param int &$line_no Current line number
  125. * @param int $end Optional end line, when we want to chop more
  126. * than one line.
  127. *
  128. * @return array The chopped lines
  129. */
  130. function _getLines(&$text_lines, &$line_no, $end = false)
  131. {
  132. if (!empty($end)) {
  133. $lines = array();
  134. // We can shift even more
  135. while ($line_no <= $end) {
  136. array_push($lines, array_shift($text_lines));
  137. $line_no++;
  138. }
  139. } else {
  140. $lines = array(array_shift($text_lines));
  141. $line_no++;
  142. }
  143. return $lines;
  144. }
  145. }