blob: a437ffc21331237108b6e1ab56fa18430583570b [file] [log] [blame]
<?php
/**
* File containing the ezcTemplateCursor class
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Template
* @version //autogen//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @access private
*/
/**
* Cursor for template parser encapsulating text and position.
*
* This class takes care of traversing a text block and keeping track of
* {@link ezcTemplateCursor::position position}, {@link ezcTemplateCursor::line line number}
* and {@link ezcTemplateCursor::column column number}.
*
* Moving the cursor position is done with gotoBeginning(), gotoEnd(),
* gotoLineBeginning(), gotoLineEnd(), advance() and gotoPosition().
* Checking the position can be done with atBeginning(), atEnd(),
* {@link ezcTemplateCursor::position position}, {@link ezcTemplateCursor::line line}
* and {@link ezcTemplateCursor::column column}.
* Matching for strings and regular expression is done with findPosition(),
* current(), subString(), match(), pregMatch() and pregMatchComplete().
*
* In addition there are methods for working with cursor objects, they are
* copy(), cursorAt() and length().
*
* This example shows how to advance through the text with different
* matching methods and print the positions.
* <code>
* $text = "hello world!";
* $c = new ezcTemplateCursor( $text );
* $c->match( "hello", true );
* echo "position: ", $c->position, "\n";
* echo "line: ", $c->line, "\n";
* echo "column: ", $c->column, "\n";
* $c->pregMatch( "/^[ ]+world/", true );
* echo "position: ", $c->position, "\n";
* echo "line: ", $c->line, "\n";
* echo "column: ", $c->column, "\n";
* echo "current: '", $c->current(), "'\n";
* </code>
*
* @package Template
* @version //autogen//
* @access private
*/
class ezcTemplateCursor
{
/**
* The text to which the cursors operates on.
* @var string
*/
public $text;
/**
* The byte position in the text, starts at 0.
* @var int
*/
public $position;
/**
* The current line number, starts at 1.
* @var int
*/
public $line;
/**
* The current column number, starts at 0.
* @var int
*/
public $column;
/**
* Initialise with text block and position data.
*
* @param string $text Text block which the cursor position relates to.
* @param int $position The string position in the text, starts at 0.
* @param int $line The line position in the text, starts at 1.
* @param int $column The column position in the text, starts at 0.
*/
public function __construct( $text, $position = 0, $line = 1, $column = 0 )
{
$this->text = $text;
$this->position = $position;
$this->line = $line;
$this->column = $column;
}
/**
* Copies all the properties of cursor $other to this cursor.
*
* @param ezcTemplateCursor $other The cursor object to copy from.
* @return void
*/
public function copy( ezcTemplateCursor $other )
{
$this->text = $other->text;
$this->position = $other->position;
$this->line = $other->line;
$this->column = $other->column;
}
/**
* Calculates the length between this cursor and the specified cursor.
* Note: If the specified cursor is before this one it returns negative values.
*
* @param ezcTemplateCursor $other The cursor to use as end position for measuring length.
* @return int
*/
public function length( ezcTemplateCursor $other )
{
return $other->position - $this->position;
}
/**
* Checks if the cursor is at the beggining of the text and returns true if
* it is.
*
* @return bool
*/
public function atBeginning()
{
return $this->position == 0;
}
/**
* Checks if the cursor is at the end of the text and returns true if it is.
*
* @return bool
* Note: The cursor is considered at the end if it is placed after the last
* character in the text ( ie. strlen( $text ) ), this is different
* from being at the last character ( strlen( $text ) - 1 ).
*/
public function atEnd()
{
return $this->position == strlen( $this->text );
}
/**
* Moves the cursor to the beginning of the text.
*
* @see atBeginning()
* @return void
*/
public function gotoBeginning()
{
$this->position = 0;
$this->line = 1;
$this->column = 0;
}
/**
* Moves the cursor to the end of the text.
*
* @see atEnd()
* @return void
*/
public function gotoEnd()
{
$endPosition = strlen( $this->text );
if ( $this->position == $endPosition )
return;
$lines = preg_split( "#\r\n|\r|\n#", substr( $this->text, $this->position ) );
if ( count( $lines ) > 0 )
{
$endLine = $this->line + count( $lines ) - 1;
$lastLine = $lines[count( $lines ) - 1];
if ( count( $lines ) > 1 )
$endColumn = strlen( $lastLine );
else
$endColumn = $this->column + strlen( $lastLine );
}
else
{
$endLine = $this->line;
$endColumn = $this->column;
}
$this->position = $endPosition;
$this->line = $endLine;
$this->column = $endColumn;
}
/**
* Moves the cursor to the beginning of the current line.
*
* @see gotoLineEnd()
* @return void
*/
public function gotoLineBeginning()
{
$this->position -= $this->column;
$this->column = 0;
}
/**
* Moves the cursor to the end of the current line.
* The end of the line is considered right before the EOL marker.
*
* Note: If the current line is at the end of the buffer the position will
* be placed at the end of the buffer similar to gotoEnd().
* @see gotoLineBeginning()
* @return void
*/
public function gotoLineEnd()
{
$endPosition = strlen( $this->text );
if ( $this->position == $endPosition )
return;
// If we find a newline we adjust endPosition to this location,
// if not we use the end of the buffer.
if ( preg_match( "#\r\n|\r|\n#", $this->text, $matches, PREG_OFFSET_CAPTURE, $this->position ) )
{
$endPosition = $matches[0][1];
}
// Increase column and set end position
$this->column += $endPosition - $this->position;
$this->position = $endPosition;
}
/**
* Moves the cursor n steps relatively to the current position.
*
* @param int $delta The number of characters to advance.
* @return void
*/
public function advance( $delta = 1 )
{
$this->gotoPosition( $this->position + $delta );
}
/**
* Moves the cursor to the specified position.
*
* @see atBeginning(), atEnd()
*
* @param int $endPosition
* @throws ezcTemplateCursorsException if the position is beyond the text.
* @return void
*/
public function gotoPosition( $endPosition )
{
if ( $endPosition > strlen( $this->text ) )
throw new ezcTemplateCursorsException();
if ( $this->position == $endPosition )
return;
/* Not supported yet, might not be needed
if ( $this->position > $endPosition )
*/
{
$lines = preg_split( "#\r\n|\r|\n#", substr( $this->text, $this->position, $endPosition - $this->position ) );
if ( count( $lines ) > 0 )
{
$endLine = $this->line + count( $lines ) - 1;
$lastLine = $lines[count( $lines ) - 1];
if ( count( $lines ) > 1 )
$endColumn = strlen( $lastLine );
else
$endColumn = $this->column + strlen( $lastLine );
}
else
{
$endLine = $this->line;
$endColumn = $this->column;
}
}
/* Not supported yet, might not be needed
else
{
$lines = preg_split( "#\r\n|\r|\n#", substr( $this->text, $endPosition, $this->position - $endPosition ) );
if ( count( $lines ) > 0 )
{
$endLine = $this->line - count( $lines ) - 1;
$lastLine = $lines[count( $lines ) - 1];
if ( $endPosition > 0 )
{
$endColumn = 0;
for ( $i = $endPosition - 1; $i > 0; --$i )
{
switch ( $this->text[$i] )
{
case "\n":
case "\r":
$endColumn = $endPosition - $i;
break 2;
}
}
}
else
{
$endColumn = 0;
}
}
else
{
$endLine = $this->line;
$endColumn = $this->column;
}
}
*/
$this->position = $endPosition;
$this->line = $endLine;
$this->column = $endColumn;
}
/**
* Extracts a substring from the current position in the text and to a given
* end position. If the position is at the end it returns false.
*
* @param string $endPosition The end position of the substring, if this is false
* it will fetch the entire substring from the current
* position.
* @return string
*/
public function subString( $endPosition = false )
{
if ( $endPosition === false )
return substr( $this->text, $this->position );
return substr( $this->text, $this->position, $endPosition - $this->position );
}
/**
* Finds the first occurence of the string $searchString in the text block from
* the current position.
*
* @param string $searchString The string to find in the text (case-sensitive).
* @param bool $checkEscaped If this is true then it will not match text which
* is preceded by the escape character \.
* @return mixed The integer value of the position or false if it is not found.
*/
public function findPosition( $searchString, $checkEscaped = false )
{
if ( $this->position === strlen( $this->text ) )
return false;
if ( !$checkEscaped )
return strpos( $this->text, $searchString, $this->position );
$position = $this->position;
while ( ( $position = strpos( $this->text, $searchString, $position ) ) !== false )
{
$escapedPos = 1;
while ( $position - $escapedPos >= 0 && $this->text[$position - $escapedPos] == "\\" )
{
$escapedPos++;
}
if ( $escapedPos % 2 == 1 )
{
break;
}
/*
if ( $this->text[$position-1] != "\\" )
break;
*/
++$position;
}
return $position;
}
/**
* Creates a clone of the current cursor at the specified position and returns it.
*
* @param int $position The position for the new cursor, line and column will be recalculated accordingly.
* @param int $newSize If this is non-false it will limit the text buffer for the new cursor.
* @return ezcTemplateCursor
*/
public function cursorAt( $position, $newSize = false )
{
if ( $position < $this->position )
throw new ezcTemplateInternalException( "Cannot move position backwards yet, sorry" );
$cursor = clone $this;
if ( $newSize !== false )
$cursor->text = substr( $cursor->text, 0, $newSize );
$cursor->gotoPosition( $position );
return $cursor;
}
/**
* Returns the character(s) at the current position. If the position is at the
* end it returns false.
*
* @param int $length The number of character to fetch from current position, negative values fetches in reverse.
* @return mixed The current character/string or false if the position is beyond the text.
* @see atEnd()
*/
public function current( $length = 1 )
{
if ( $this->position === strlen( $this->text ) )
return false;
return substr( $this->text, $this->position, $length );
}
/**
* Performs a preg_match() on the current position to figure out if the
* pattern matches. The preg_match() will be called with PREG_OFFSET_CAPTURE
* so the return matches will contain the offset as well, however the offset
* will be relative to the starting position and not the start of the buffer.
*
* Note: To check that a pattern matches immediately at the start position use the
* ^ in the pattern string.
*
* @param string $pattern The pattern which is passed to preg_match().
* @return mixed The result of the preg_match() if successful or false it it failed.
*/
public function pregMatchComplete( $pattern )
{
if ( $this->position === strlen( $this->text ) )
return false;
if ( !preg_match( $pattern, substr( $this->text, $this->position ), $matches, PREG_OFFSET_CAPTURE ) )
return false;
return $matches;
}
/**
* Does a pregMatchComplete and returns only the [0][0] part.
*
* @param string $pattern The pattern which is passed to preg_match().
* @param string $advance If true it will advance the position to after matched text.
* @return mixed The [0][0] entry of the preg_match() result if successful or false it it failed.
*/
public function pregMatch( $pattern, $advance = true )
{
$matches = $this->pregMatchComplete( $pattern );
if ( $matches === false )
{
return false;
}
if ( $advance )
{
$this->advance( strlen( $matches[0][0] ) );
}
return $matches[0][0];
}
/**
* Matches the specified text with the text at the current position and returns the result.
*
* @param string $word The character/string to match at the current position.
* @param string $advance If true it will advance the position to after matched text.
* @return bool True if the $word matches, otherwise false.
*/
public function match( $word, $advance = true )
{
$len = strlen( $word );
if ( $this->current( $len ) == $word )
{
if ( $advance ) $this->advance( $len );
return true;
}
return false;
}
}
?>