@author Paul Butler <github@paulbutler.org>
@author Mike Robinson
@author Jonathan Beliën <jbe@geo6.be>

@see https://github.com/paulgb/simplediff/blob/master/php/simplediff.php
502153
public static function renderDiff(array $diff): array
    {
        $returnOld = '';
        $returnNew = '';

        foreach ($diff as $k) {
            if (is_array($k)) {
                $returnOld .= (!empty($k['deleted']) ? '<del>'.implode(' ', $k['deleted']).'</del> ' : '');
                $returnNew .= (!empty($k['inserted']) ? '<ins>'.implode(' ', $k['inserted']).'</ins> ' : '');
            } else {
                $returnOld .= $k.' ';
                $returnNew .= $k.' ';
            }
        }

        return [
            'old' => $returnOld,
            'new' => $returnNew,
        ];
    }
886734
private function diff(array $a, array $b)
    {
        if (empty($a)) {
            $a = [];
        }
        if (empty($b)) {
            $b = [];
        }
        $keys = array_keys($a + $b);
        $result = [];

        foreach ($keys as $k) {
            if (empty($a[$k])) {
                $l = null;
            } else {
                $l = $a[$k];
            }
            if (empty($b[$k])) {
                $r = null;
            } else {
                $r = $b[$k];
            }
            if ($l != $r) {
                $result[] = [$k, $l, $r];
            }
        }

        return $result;
    }
1287942
public function actionInit()
    {
        // username
        $username = $this->getConfig(self::CONFIG_VAR_USERNAME);
        if (!$username) {
            $username = $this->prompt('Whats your Github username?');
            $this->saveConfig(self::CONFIG_VAR_USERNAME, $username);
        }
        
        // clonetype
        $cloneType = $this->getConfig(self::CONFIG_VAR_CLONETYPE);
        if (!$cloneType) {
            $cloneType = $this->select('Are you connected via ssh or https?', ['ssh' => 'ssh', 'http' => 'http']);
            $this->saveConfig(self::CONFIG_VAR_CLONETYPE, $cloneType);
        }
        
        $summary = [];
        $itemWithoutFork = false;
        
        // generate summary overview
        foreach ($this->repos as $repo) {
            $newRepoHome = $this->getFilesystemRepoPath($repo);
            if (file_exists($newRepoHome . DIRECTORY_SEPARATOR . '.git')) {
                $summary[] = $this->summaryItem($repo, false, true);
            } elseif ($this->forkExists($username, $repo)) {
                $summary[] = $this->summaryItem($repo, true, false);
            } else {
                $itemWithoutFork = true;
                $summary[] = $this->summaryItem($repo, false, false);
            }
        }
        
        if ($itemWithoutFork) {
            Console::clearScreen();
            $this->outputInfo($this->markdown($this->text));
            foreach ($summary as $sum) {
                if (!$sum[2] && !$sum[1]) {
                    $this->outputInfo($this->markdown("**{$sum[0]}**: https://github.com/luyadev/{$sum[0]}/fork", true));
                }
            }
            echo (new Table())->setHeaders(['Repo', 'Already initialized', 'Fork exists'])->setRows($summary)->run();
            $this->outputError("Repos without fork detected. Those repos will be initialized as READ ONLY. It means you can not push any changes to them.");
            
            if (!$this->confirm("Continue?")) {
                return $this->outputError('Abort by User.');
            }
        }
        
        // foreach summary and clone
        foreach ($summary as $sum) {
            $repo = $sum[0];
            $hasFork = $sum[2];
            $exists = $sum[1];
            
            // continue already initialized repos.
            if ($exists) {
                continue;
            }
            
            $newRepoHome = $this->getFilesystemRepoPath($repo);
            
            if ($hasFork) {
                $cloneUrl = ($cloneType == 'ssh') ? "git@github.com:{$username}/{$repo}.git" : "https://github.com/{$username}/{$repo}.git";
            } else {
                $cloneUrl = ($cloneType == 'ssh') ? "git@github.com:luyadev/{$repo}.git" : "https://github.com/{$username}/{$repo}.git";
            }
            
            $this->cloneRepo($repo, $cloneUrl, $newRepoHome, 'luyadev');
        }
        
        return $this->outputSuccess("init complete.");
    }
894263
function modify( $tpl, $operatorName, $operatorParameters, $rootNamespace, $currentNamespace, &$value, $namedParameters, $placement )
    {
        if ( isset( $this->PHPNameMap[$operatorName] ) )
        {
            $typeFunction = $this->PHPNameMap[$operatorName];
            $this->checkType( $typeFunction, $tpl, $value, $operatorParameters, $rootNamespace, $currentNamespace, $placement );
            return;
        }
        switch ( $operatorName )
        {
            case $this->IsClassName:
            {
                if ( count( $operatorParameters ) == 1 )
                {
                    $className = $tpl->elementValue( $operatorParameters[0], $rootNamespace, $currentNamespace, $placement );
                    $value = strtolower( get_class( $value ) ) == strtolower( $className );
                }
                else
                {
                    $className = $tpl->elementValue( $operatorParameters[0], $rootNamespace, $currentNamespace, $placement );
                    $value = strtolower( get_class( $tpl->elementValue( $operatorParameters[1], $rootNamespace, $currentNamespace, $placement ) ) ) == strtolower( $className );
                }
            } break;
            case $this->IsSetName:
            {
                if ( count( $operatorParameters ) > 0 )
                {
                    if ( count( $operatorParameters ) > 1 )
                    {
                        $tpl->extraParameters( $operatorName, count( $operatorParameters ), 1 );
                    }

                    $operand = $tpl->elementValue( $operatorParameters[0], $rootNamespace, $currentNamespace, $placement, true );
                    $value = $operand !== null;


                }
                else
                    $tpl->missingParameter( $operatorName, 'input' );

            } break;
            case $this->IsUnsetName:
            {
                if ( count( $operatorParameters ) > 0 )
                {
                    if ( count( $operatorParameters ) > 1 )
                        $tpl->extraParameters( $operatorName,
                                               count( $operatorParameters ),
                                               1 );
                    $operand = $tpl->elementValue( $operatorParameters[0], $rootNamespace, $currentNamespace, $placement, true );
                    $value = $operand === null;
                }
                else
                    $tpl->missingParameter( $operatorName, 'input' );
            } break;
            case $this->GetTypeName:
            {
                if ( count( $operatorParameters ) > 0 )
                {
                    if ( count( $operatorParameters ) > 1 )
                        $tpl->extraParameters( $operatorName,
                                               count( $operatorParameters ),
                                               1 );
                    $operand = $tpl->elementValue( $operatorParameters[0], $rootNamespace, $currentNamespace, $placement );
                }
                else
                    $operand =& $value;
                if ( $operand === null )
                    $value = 'null';
                else if ( is_bool( $operand ) )
                    $value = 'boolean[' . ( $operand ? 'true' : 'false' ) . ']';
                else if ( is_object( $operand ) )
                    $value = 'object[' . get_class( $operand ) . ']';
                else if ( is_array( $operand ) )
                    $value = 'array[' . count( $operand ) . ']';
                else if ( is_string( $operand ) )
                    $value = 'string[' . strlen( $operand ) . ']';
                else
                    $value = gettype( $operand );
            } break;
            case $this->GetClassName:
            {
                if ( count( $operatorParameters ) > 0 )
                {
                    if ( count( $operatorParameters ) > 1 )
                        $tpl->extraParameters( $operatorName,
                                               count( $operatorParameters ),
                                               1 );
                    $operand = $tpl->elementValue( $operatorParameters[0], $rootNamespace, $currentNamespace, $placement );
                    $value = strtolower( get_class( $operand ) );
                }
                else
                {
                    $value = strtolower( get_class( $value ) );
                }
            } break;
        }
    }
882188
function process( $tpl, &$textElements, $functionName, $functionChildren, $functionParameters, $functionPlacement, $rootNamespace, $currentNamespace )
    {
        $params = $functionParameters;
        if ( !isset( $params["uri"] ) )
        {
            $tpl->missingParameter( $this->IncludeName, "uri" );
            return false;
        }
        $uri = $tpl->elementValue( $params["uri"], $rootNamespace, $currentNamespace, $functionPlacement );
        $name = "";
        if ( isset( $params["name"] ) )
            $name = $tpl->elementValue( $params["name"], $rootNamespace, $currentNamespace, $functionPlacement );
        if ( $currentNamespace != "" )
        {
            if ( $name != "" )
                $name = "$currentNamespace:$name";
            else
                $name = $currentNamespace;
        }
        reset( $params );
        $whatParamsShouldBeUnset = array();
        $whatParamsShouldBeReplaced = array();
        while ( ( $key = key( $params ) ) !== null )
        {
            $item =& $params[$key];
            switch ( $key )
            {
                case "name":
                case "uri":
                    break;

                default:
                {
                    if ( !$tpl->hasVariable( $key, $name ) )
                    {
                        $whatParamsShouldBeUnset[] = $key; // Tpl vars should be removed after including
                    }
                    else
                    {
                        $whatParamsShouldBeReplaced[$key] = $tpl->variable( $key, $name ); // Tpl vars should be replaced after including
                    }

                    $item_value = $tpl->elementValue( $item, $rootNamespace, $currentNamespace, $functionPlacement );
                    $tpl->setVariable( $key, $item_value, $name );
                } break;
            }
            next( $params );
        }
        eZTemplateIncludeFunction::handleInclude( $textElements, $uri, $tpl, $rootNamespace, $name );
        // unset var
        foreach ( $whatParamsShouldBeUnset as $key )
        {
            $tpl->unsetVariable( $key, $name );
        }
        // replace var
        foreach ( $whatParamsShouldBeReplaced as $key => $item_value )
        {
            $tpl->setVariable( $key, $item_value, $name );
        }
    }
209010
static function snippet($text, $keywords, $limit, $max_length, $around = array('<b>', '</b>'))
	{
		$result = array();

		$keywords = self::normalize($keywords);
		list($pre, $post) = $around;
		$prev = array();
		$after = false;

		foreach(self::normalize_hash($text) as $word_orig => $word_norm)
		{
			$is_kw = false;
			if(in_array($word_norm, $keywords))
			{
				$prev[] = $pre.$word_orig.$post;
				$is_kw = true;
			}
			else
				$prev[] = $word_orig;

			if($is_kw)
			{
				if($after)
				{
					$result = array_merge($result, $prev);
					$prev = array();
				}
				else
				{
					$result = array_merge($result, count($prev) > $limit ? array('&#133') : array(), array_slice($prev, -$limit-1));
					$prev = array();
				}

				$after = true;
			}
			else
			{
				if($after && count($prev) >= $limit)
				{
					$after = false;
					$result = array_merge($result, $prev);
					$prev = array();
				}
			}
		}

		if($after)
			$result = array_merge($result, array('&#133'), $prev);

		return strip_text(join(' ', $result).'&#133;', $max_length);
	}
863157
static function processElementTransformationList( $tpl, &$node, $elements, &$privateData )
    {
        $useComments = $privateData['use-comments'];
        $php =& $privateData['php-creator'];
        $resourceData =& $privateData['resource-data'];
        $elementTree = $elements;
        $newElements = eZTemplateCompiler::processElementTransformationChild( $useComments, $php, $tpl, $node,
                                                                              $elementTree, $elements, $resourceData );
        if ( $newElements )
            return $newElements;
        return $elements;
    }
417724
static function arrayMergeRecursive()
    {
        $params = func_get_args();
        $return = array_shift($params);
        foreach ($params as $array){
            foreach ($array as $key => $value){
                if (isset($return[$key]) && is_array($value) && is_array($return[$key])){
                    $return[$key] = self::arrayMergeRecursive($return[$key], $value);
                }else{
                    $return[$key] = $value;
                }
            }
        }
        return $return;
    }
117314
function linkedPhp()
    {
        $matches = $this->getParsedLinkedPhp();
        $resolvedPhpVersion = $matches[3] ?: $matches[2];

        return $this->supportedPhpVersions()->first(   
            function ($version) use ($resolvedPhpVersion) {
                $resolvedVersionNormalized = preg_replace('/[^\d]/', '', $resolvedPhpVersion);
                $versionNormalized = preg_replace('/[^\d]/', '', $version);
                return $resolvedVersionNormalized === $versionNormalized;
        }, function () use ($resolvedPhpVersion) {
            throw new DomainException("Unable to determine linked PHP when parsing '$resolvedPhpVersion'");
        });
    }
1157888
public function get_diff()
	{
		$diff = array(0 => array(), 1 => array());
		foreach ($this->_data as $key => $val)
		{
			if ($this->is_changed($key))
			{
				$diff[0][$key] = array_key_exists($key, $this->_original) ? $this->_original[$key] : null;
				$diff[1][$key] = $val;
			}
		}
		foreach ($this->_data_relations as $key => $val)
		{
			$rel = static::relations($key);
			if ($rel->singular)
			{
				$new_pk = null;
				if (empty($this->_original_relations[$key]) !== empty($val)
					or ( ! empty($this->_original_relations[$key]) and ! empty($val)
						and $this->_original_relations[$key] !== $new_pk = $val->implode_pk($val)
					))
				{
					$diff[0][$key] = isset($this->_original_relations[$key]) ? $this->_original_relations[$key] : null;
					$diff[1][$key] = isset($val) ? $new_pk : null;
				}
			}
			else
			{
				$original_pks = empty($this->_original_relations[$key]) ? array() : $this->_original_relations[$key];
				$new_pks = array();
				if ($val)
				{
					foreach ($val as $v)
					{
						if ( ! in_array(($new_pk = $v->implode_pk($v)), $original_pks))
						{
							$new_pks[] = $new_pk;
						}
						else
						{
							$original_pks = array_diff($original_pks, array($new_pk));
						}
					}
				}
				if ( ! empty($original_pks) or ! empty($new_pks)) {
					$diff[0][$key] = empty($original_pks) ? null : $original_pks;
					$diff[1][$key] = empty($new_pks) ? null : $new_pks;
				}
			}
		}

		return $diff;
	}
732752
public function getInsertDiff()
    {
        return array_udiff_assoc(
            $this->collection->toArray(),
            $this->snapshot,
            function($a, $b) { return $a === $b ? 0 : 1; }
        );
    }
1271854
public function apply()
	{
		foreach ($this->patches as $patch)
		{
			// Separate the input into lines
			$lines = self::splitLines($patch['udiff']);

			// Loop for each header
			while (self::findHeader($lines, $src, $dst))
			{
				$done = false;

				if ($patch['strip'] === null)
				{
					$src = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $src);
					$dst = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $dst);
				}
				else
				{
					$src = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $src);
					$dst = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $dst);
				}

				// Loop for each hunk of differences
				while (self::findHunk($lines, $src_line, $src_size, $dst_line, $dst_size))
				{
					$done = true;

					// Apply the hunk of differences
					$this->applyHunk($lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size);
				}

				// If no modifications were found, throw an exception
				if (!$done)
				{
					throw new \RuntimeException('Invalid Diff');
				}
			}
		}

		// Initialize the counter
		$done = 0;

		// Patch each destination file
		foreach ($this->destinations as $file => $content)
		{
			$content = implode("\n", $content);

			if (File::write($file, $content))
			{
				if (isset($this->sources[$file]))
				{
					$this->sources[$file] = $content;
				}

				$done++;
			}
		}

		// Remove each removed file
		foreach ($this->removals as $file)
		{
			if (File::delete($file))
			{
				if (isset($this->sources[$file]))
				{
					unset($this->sources[$file]);
				}

				$done++;
			}
		}

		// Clear the destinations cache
		$this->destinations = array();

		// Clear the removals
		$this->removals = array();

		// Clear the patches
		$this->patches = array();

		return $done;
	}
1053618
function github(&$rootDoc, $formatter) {

        // set doclet options
        $this->_rootDoc = & $rootDoc;
        $phpdoctor = & $rootDoc->phpdoctor();
        $options = & $rootDoc->options();

        $this->formatter = $formatter;

        if (isset($options["d"])) {
            $this->_d = $phpdoctor->makeAbsolutePath($options["d"], $phpdoctor->sourcePath());
        } elseif (isset($options["output_dir"])) {
            $this->_d = $phpdoctor->makeAbsolutePath($options["output_dir"], $phpdoctor->sourcePath());
        } else {
            $this->_d = $phpdoctor->makeAbsolutePath("apidocs", $phpdoctor->sourcePath());
        }
        $this->_d = $phpdoctor->fixPath($this->_d);

        if (is_dir($this->_d)) {
            $phpdoctor->warning("Output directory already exists, overwriting");
        } else {
            mkdir($this->_d);
        }
        $phpdoctor->verbose("Setting output directory to " . $this->_d . '"');

        if (isset($options["windowtitle"]))
            $this->_windowTitle = $options["windowtitle"];
        if (isset($options["doctitle"]))
            $this->_docTitle = $options["doctitle"];
        if (isset($options["header"]))
            $this->_header = $options["header"];
        if (isset($options["footer"]))
            $this->_footer = $options["footer"];
        if (isset($options["bottom"]))
            $this->_bottom = $options["bottom"];

        if (isset($options["tree"]))
            $this->_tree = $options["tree"];

        $this->_includeSource = false;

        // write frame
        //$frameOutputWriter = & new frameOutputWriter($this);
        // write overview summary
        //$packageIndexWriter = & new packageIndexWriter($this);
        // write package overview frame
        //$packageIndexFrameWriter = & new packageIndexFrameWriter($this);
        // write package summaries
        $packageWriter = & new packageWriter($this);

        // write package frame
        //$packageFrameWriter = & new packageFrameWriter($this);
        // write classes
        $classWriter = & new classWriter($this);

        // write global functions
        $functionWriter = & new functionWriter($this);

        // write global variables
        $globalWriter = & new globalWriter($this);

        // write index
        $indexWriter = & new indexWriter($this);
        // write deprecated index
        $deprecatedWriter = & new deprecatedWriter($this);

        // write todo index
        $todoWriter = & new todoWriter($this);

//        // copy README.md
//        $phpdoctor->message("Copying README.md");
//
//        if (is_file($phpdoctor->sourcePath() . "README.md")) {
//            copy($phpdoctor->sourcePath() . "README.md", $this->_d . "README.md");
//        }else if(is_file($phpdoctor->sourcePath() . "README")){
//            copy($phpdoctor->sourcePath() . "README", $this->_d . "README.md");
//        }
    }
1361390
private function diffTableMergeQueries(
        $table,
        &$fields,
        &$foQueries,
        &$soQueries
    ) {
        $key = $this->diffTableFieldPrimaryKey($fields);

        if ($key && count($foQueries) > 0) {
            $writer = $this->getWriter();

            $foQueries[] = $writer->alterTableDropPrimaryKey($table);

            $fields[$key]['Key'] = '';
            $fields[$key]['Extra'] = '';

            $foQueries[] = $writer->alterTableChange(
                $table,
                $key,
                $fields[$key]
            );
        }

        return array_merge(array_reverse($foQueries), $soQueries);
    }
659522
static function init()
  {
    if (self::$initialized) return;

    self::$initialized = TRUE;

    self::$COFFEE_KEYWORDS  = array_merge(self::$COFFEE_KEYWORDS, array_keys(self::$COFFEE_ALIASES));
    self::$COFFEE_RESERVED  = array_merge(self::$JS_RESERVED, self::$JS_KEYWORDS, self::$COFFEE_KEYWORDS, self::$STRICT_PROSCRIBED);
    self::$JS_FORBIDDEN     = array_merge(self::$JS_KEYWORDS, self::$JS_RESERVED, self::$STRICT_PROSCRIBED);
    self::$INDEXABLE        = array_merge(self::$CALLABLE, self::$INDEXABLE);
    self::$NOT_SPACED_REGEX = array_merge(self::$NOT_REGEX, self::$NOT_SPACED_REGEX);

    Rewriter::init();

    self::$INVERSES         = Rewriter::$INVERSES;
  }
313442
public function compare() {
        $compareContent=false;
        
        
        //Get the Main Revision
        $snippet1=Snippet::get()->byID(intval($this->urlParams['ID']));
        if(empty($snippet1) || $snippet1===false || $snippet1->ID==0) {
            $snippet1=false;
        }
        
        if($snippet1!==false) {
            //Get the Comparision Revision
            $snippet2=$snippet1->Version(intval($this->urlParams['OtherID']));
            if(empty($snippet2) || $snippet1===false || $snippet2->ID==0) {
                $snippet2=false;
            }
            
            if($snippet2!==false) {
                $snippet1Text=preg_replace('/\r\n|\n|\r/', "\n", $snippet1->SnippetText);
                $snippet2Text=preg_replace('/\r\n|\n|\r/', "\n", $snippet2->Text);
                
                //Generate the diff file
                $diff=new Text_Diff('auto', array(preg_split('/\n/', $snippet1Text), preg_split('/\n/', $snippet2Text)));
                $renderer=new WP_Text_Diff_Renderer_Table();
                
                $renderedDiff=$renderer->render($diff);
                if(!empty($renderedDiff)) {
                    $lTable='<table cellspacing="0" cellpadding="0" border="0" class="diff">'.
                                '<colgroup>'.
                                    '<col class="ltype"/>'.
                                    '<col class="content"/>'.
                                '</colgroup>'.
                                '<tbody>';
                    $rTable=$lTable;
                    
                    header('content-type: text/plain');
                    $xml=simplexml_load_string('<tbody>'.str_replace('&nbsp;', ' ', $renderedDiff).'</tbody>');
                    foreach($xml->children() as $row) {
                        $i=0;
                        $lTable.='<tr>';
                        $rTable.='<tr>';
                        
                        foreach($row->children() as $td) {
                            $attr=$td->attributes();
                            
                            if($i==0) {
                                $lTable.=$td->asXML();
                            }else {
                                $rTable.=$td->asXML();
                            }
                            
                            $i++;
                        }
                        
                        $lTable.='</tr>';
                        $rTable.='</tr>';
                    }
                    
                    $lTable.='</tbody></table>';
                    $rTable.='</tbody></table>';
                    
                    $compareContent='<div class="compare leftSide">'.$lTable.'</div>'.
                                    '<div class="compare rightSide">'.$rTable.'</div>';
                }
            }
        }
        
        
        Requirements::css(CB_DIR.'/css/CompareView.css');
        Requirements::javascript(CB_DIR.'/javascript/CodeBank.CompareView.js');
        
        
        return $this->renderWith('CodeBank_CompareView', array(
                                                                'CompareContent'=>$compareContent
                                                            ));
    }
213197
function preProcess( $oldArray, $newArray )
    {
        $substr = $this->substringMatrix( $oldArray, $newArray );

        $nOldWords = count( $oldArray );
        $nNewWords = count( $newArray );

        /*
        $tmp = $substr['lengthMatrix'];
        print( "<pre>" );
        $this->dumpMatrix( $tmp, $nOldWords, $nNewWords );
        print( "</pre>" );
        */

        $strings = $this->substrings( $substr, $oldArray, $newArray );

        //Merge detected paragraphs
        $mergedStrings = array();
        foreach ( $strings as $sstring )
        {
            $mergedStrings = $mergedStrings + $sstring;
        }
        unset( $strings );

        //Check for changes in lead & inner paragraphs
        $offset = 0;
        $delOffset = 0;
        $internalOffset = 0;
        $merged = array();

        $oldOffset = -1;

        foreach ( $mergedStrings as $key => $wordArray )
        {
            if ( $oldOffset >= $wordArray['oldOffset'] )
            {
                continue;
            }

            $oldOffset = $wordArray['oldOffset'];

            //Check for inserted paragraphs
            $nk = $internalOffset;
            while ( $key > $offset )
            {
                $merged[$nk] = array( $offset => 'added' );
                $nk++;
                $offset++;
            }

            //Check for removed paragraphs
            $k = $internalOffset;
            while ( $oldOffset > $delOffset )
            {
                if ( $k < $nk )
                {
                    // Paragraph is changed
                    if ( array_key_exists( $delOffset, $merged[$k] ) )
                    {
                        // Old & new paragraph places is the same
                        $merged[$k][$delOffset] = 'changed';
                    }
                    else
                    {
                        $merged[$k][$delOffset] = 'removed';
                    }
                }
                else
                {
                    $merged[$k] = array( $delOffset => 'removed' );
                }
                $k++;
                $delOffset++;
            }

            $internalOffset = ($k > $nk) ? $k:$nk;

            //The default state - unchanged paragraph
            $merged[$internalOffset] = array( $key => 'unchanged' );
            $internalOffset++;
            $delOffset++;
            $offset++;
        }

        //Check for appended paragraphs
        $nk = $internalOffset;
        while ( $nNewWords > $offset )
        {
            $merged[$nk] = array( $offset => 'added' );
            $nk++;
            $offset++;
        }

        //Check for end-deleted paragraphs
        $k = $internalOffset;
        while ( $nOldWords > $delOffset )
        {
            if ( $k < $nk )
            {
                if ( array_key_exists( $delOffset, $merged[$k] ) )
                {
                    $merged[$k][$delOffset] = 'changed';
                }
                else
                {
                    $merged[$k][$delOffset] = 'removed';
                }
            }
            else
            {
                $merged[$k] = array( $delOffset => 'removed' );
            }
            $k++;
            $delOffset++;
        }

        return $merged;
    }