shell.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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.3 2008/01/04 10:37:27 jan Exp $
  9. *
  10. * Copyright 2007-2008 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 integer &$line_no Current line number
  125. * @param integer $end Optional end line, when we want to chop more than one line.
  126. * @return array The chopped lines
  127. */
  128. function _getLines(&$text_lines, &$line_no, $end = false)
  129. {
  130. if (!empty($end)) {
  131. $lines = array();
  132. // We can shift even more
  133. while ($line_no <= $end) {
  134. array_push($lines, array_shift($text_lines));
  135. $line_no++;
  136. }
  137. } else {
  138. $lines = array(array_shift($text_lines));
  139. $line_no++;
  140. }
  141. return $lines;
  142. }
  143. }