Overview

Packages

  • Text
    • Diff

Classes

  • Horde_Text_Diff
  • Horde_Text_Diff_Engine_Native
  • Horde_Text_Diff_Engine_Shell
  • Horde_Text_Diff_Engine_String
  • Horde_Text_Diff_Engine_Xdiff
  • Horde_Text_Diff_Exception
  • Horde_Text_Diff_Mapped
  • Horde_Text_Diff_Op_Add
  • Horde_Text_Diff_Op_Base
  • Horde_Text_Diff_Op_Change
  • Horde_Text_Diff_Op_Copy
  • Horde_Text_Diff_Op_Delete
  • Horde_Text_Diff_Renderer
  • Horde_Text_Diff_Renderer_Context
  • Horde_Text_Diff_Renderer_Inline
  • Horde_Text_Diff_Renderer_Unified
  • Horde_Text_Diff_ThreeWay
  • Horde_Text_Diff_ThreeWay_BlockBuilder
  • Horde_Text_Diff_ThreeWay_Op_Base
  • Horde_Text_Diff_ThreeWay_Op_Copy
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * "Inline" diff renderer.
  4:  *
  5:  * This class renders diffs in the Wiki-style "inline" format.
  6:  *
  7:  * Copyright 2004-2012 Horde LLC (http://www.horde.org/)
  8:  *
  9:  * See the enclosed file COPYING for license information (LGPL). If you did
 10:  * not receive this file, see http://www.horde.org/licenses/lgpl21.
 11:  *
 12:  * @author  Ciprian Popovici
 13:  * @package Text_Diff
 14:  */
 15: class Horde_Text_Diff_Renderer_Inline extends Horde_Text_Diff_Renderer
 16: {
 17:     /**
 18:      * Number of leading context "lines" to preserve.
 19:      *
 20:      * @var integer
 21:      */
 22:     protected $_leading_context_lines = 10000;
 23: 
 24:     /**
 25:      * Number of trailing context "lines" to preserve.
 26:      *
 27:      * @var integer
 28:      */
 29:     protected $_trailing_context_lines = 10000;
 30: 
 31:     /**
 32:      * Prefix for inserted text.
 33:      *
 34:      * @var string
 35:      */
 36:     protected $_ins_prefix = '<ins>';
 37: 
 38:     /**
 39:      * Suffix for inserted text.
 40:      *
 41:      * @var string
 42:      */
 43:     protected $_ins_suffix = '</ins>';
 44: 
 45:     /**
 46:      * Prefix for deleted text.
 47:      *
 48:      * @var string
 49:      */
 50:     protected $_del_prefix = '<del>';
 51: 
 52:     /**
 53:      * Suffix for deleted text.
 54:      *
 55:      * @var string
 56:      */
 57:     protected $_del_suffix = '</del>';
 58: 
 59:     /**
 60:      * Header for each change block.
 61:      *
 62:      * @var string
 63:      */
 64:     protected $_block_header = '';
 65: 
 66:     /**
 67:      * Whether to split down to character-level.
 68:      *
 69:      * @var boolean
 70:      */
 71:     protected $_split_characters = false;
 72: 
 73:     /**
 74:      * What are we currently splitting on? Used to recurse to show word-level
 75:      * or character-level changes.
 76:      *
 77:      * @var string
 78:      */
 79:     protected $_split_level = 'lines';
 80: 
 81:     protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
 82:     {
 83:         return $this->_block_header;
 84:     }
 85: 
 86:     protected function _startBlock($header)
 87:     {
 88:         return $header;
 89:     }
 90: 
 91:     protected function _lines($lines, $prefix = ' ', $encode = true)
 92:     {
 93:         if ($encode) {
 94:             array_walk($lines, array(&$this, '_encode'));
 95:         }
 96: 
 97:         if ($this->_split_level == 'lines') {
 98:             return implode("\n", $lines) . "\n";
 99:         } else {
100:             return implode('', $lines);
101:         }
102:     }
103: 
104:     protected function _added($lines)
105:     {
106:         array_walk($lines, array(&$this, '_encode'));
107:         $lines[0] = $this->_ins_prefix . $lines[0];
108:         $lines[count($lines) - 1] .= $this->_ins_suffix;
109:         return $this->_lines($lines, ' ', false);
110:     }
111: 
112:     protected function _deleted($lines, $words = false)
113:     {
114:         array_walk($lines, array(&$this, '_encode'));
115:         $lines[0] = $this->_del_prefix . $lines[0];
116:         $lines[count($lines) - 1] .= $this->_del_suffix;
117:         return $this->_lines($lines, ' ', false);
118:     }
119: 
120:     protected function _changed($orig, $final)
121:     {
122:         /* If we've already split on characters, just display. */
123:         if ($this->_split_level == 'characters') {
124:             return $this->_deleted($orig)
125:                 . $this->_added($final);
126:         }
127: 
128:         /* If we've already split on words, just display. */
129:         if ($this->_split_level == 'words') {
130:             $prefix = '';
131:             while ($orig[0] !== false && $final[0] !== false &&
132:                    substr($orig[0], 0, 1) == ' ' &&
133:                    substr($final[0], 0, 1) == ' ') {
134:                 $prefix .= substr($orig[0], 0, 1);
135:                 $orig[0] = substr($orig[0], 1);
136:                 $final[0] = substr($final[0], 1);
137:             }
138:             return $prefix . $this->_deleted($orig) . $this->_added($final);
139:         }
140: 
141:         $text1 = implode("\n", $orig);
142:         $text2 = implode("\n", $final);
143: 
144:         /* Non-printing newline marker. */
145:         $nl = "\0";
146: 
147:         if ($this->_split_characters) {
148:             $diff = new Horde_Text_Diff('native',
149:                                   array(preg_split('//', $text1),
150:                                         preg_split('//', $text2)));
151:         } else {
152:             /* We want to split on word boundaries, but we need to preserve
153:              * whitespace as well. Therefore we split on words, but include
154:              * all blocks of whitespace in the wordlist. */
155:             $diff = new Horde_Text_Diff('native',
156:                                   array($this->_splitOnWords($text1, $nl),
157:                                         $this->_splitOnWords($text2, $nl)));
158:         }
159: 
160:         /* Get the diff in inline format. */
161:         $renderer = new Horde_Text_Diff_Renderer_inline
162:             (array_merge($this->getParams(),
163:                          array('split_level' => $this->_split_characters ? 'characters' : 'words')));
164: 
165:         /* Run the diff and get the output. */
166:         return str_replace($nl, "\n", $renderer->render($diff)) . "\n";
167:     }
168: 
169:     protected function _splitOnWords($string, $newlineEscape = "\n")
170:     {
171:         // Ignore \0; otherwise the while loop will never finish.
172:         $string = str_replace("\0", '', $string);
173: 
174:         $words = array();
175:         $length = strlen($string);
176:         $pos = 0;
177: 
178:         while ($pos < $length) {
179:             // Eat a word with any preceding whitespace.
180:             $spaces = strspn(substr($string, $pos), " \n");
181:             $nextpos = strcspn(substr($string, $pos + $spaces), " \n");
182:             $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos));
183:             $pos += $spaces + $nextpos;
184:         }
185: 
186:         return $words;
187:     }
188: 
189:     protected function _encode(&$string)
190:     {
191:         $string = htmlspecialchars($string);
192:     }
193: }
194: 
API documentation generated by ApiGen