The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package CSS::Orientation;

use strict;
use warnings;

our $VERSION = '0.01';

use base qw( Exporter );

our %EXPORT_TAGS = ( 'all' => [ qw(
    ChangeLeftToRightToLeft
) ] );

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

# h                       [0-9a-f]      ; a hexadecimal digit
our $HEX = q'[0-9a-f]';

# nonascii                [\200-\377]
our $NON_ASCII = q'[\200-\377]';

# unicode                 \\{h}{1,6}(\r\n|[ \t\r\n\f])?
our $UNICODE = q'(?:(?:\\' . $HEX . q'{1,6})(?:\r\n|[ \t\r\n\f])?)';

# escape                  {unicode}|\\[^\r\n\f0-9a-f]
our $ESCAPE = q'(?:' . $UNICODE . q'|\\[^\r\n\f0-9a-f])';

# nmstart                 [_a-z]|{nonascii}|{escape}
our $NMSTART = q'(?:[_a-z]|' . $NON_ASCII . q'|' . $ESCAPE . q')';

# nmchar                  [_a-z0-9-]|{nonascii}|{escape}
our $NMCHAR = q'(?:[_a-z0-9-]|' . $NON_ASCII . q'|' . $ESCAPE . q')';

# ident                   -?{nmstart}{nmchar}*
our $IDENT = q'-?' . $NMSTART . $NMCHAR . '*';

# name                    {nmchar}+
our $NAME = $NMCHAR . q'+';

# hash
our $HASH = q'#' . $NAME;

# num                     [0-9]+|[0-9]*"."[0-9]+
our $NUM = q'(?:[0-9]*\.[0-9]+|[0-9]+)';

# s                       [ \t\r\n\f]
our $SPACE = q'[ \t\r\n\f]';

# w                       {s}*
our $WHITESPACE = '(?:' . $SPACE . q'*)';

# url special chars
our $URL_SPECIAL_CHARS = q'[!#$%&*-~]';

# url chars               ({url_special_chars}|{nonascii}|{escape})*
our $URL_CHARS = sprintf( q'(?:%s|%s|%s)*', $URL_SPECIAL_CHARS, $NON_ASCII, $ESCAPE );

# comments
# see http://www.w3.org/TR/CSS21/grammar.html
our $COMMENT = q'/\*[^*]*\*+([^/*][^*]*\*+)*/';

# {E}{M}             {return EMS;}
# {E}{X}             {return EXS;}
# {P}{X}             {return LENGTH;}
# {C}{M}             {return LENGTH;}
# {M}{M}             {return LENGTH;}
# {I}{N}             {return LENGTH;}
# {P}{T}             {return LENGTH;}
# {P}{C}             {return LENGTH;}
# {D}{E}{G}          {return ANGLE;}
# {R}{A}{D}          {return ANGLE;}
# {G}{R}{A}{D}       {return ANGLE;}
# {M}{S}             {return TIME;}
# {S}                {return TIME;}
# {H}{Z}             {return FREQ;}
# {K}{H}{Z}          {return FREQ;}
# %                  {return PERCENTAGE;}
our $UNIT = q'(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)';

# {num}{UNIT|IDENT}                   {return NUMBER;}
our $QUANTITY = sprintf( '%s(?:%s%s|%s)?', $NUM, $WHITESPACE, $UNIT, $IDENT );




# Generic token delimiter character.
our $TOKEN_DELIMITER = '~';

# This is a temporary match token we use when swapping strings.
our $TMP_TOKEN = sprintf( '%sTMP%s', $TOKEN_DELIMITER, $TOKEN_DELIMITER );

# Token to be used for joining lines.
our $TOKEN_LINES = sprintf( '%sJ%s', $TOKEN_DELIMITER, $TOKEN_DELIMITER );

# Global constant text strings for CSS value matches.
our $LTR = 'ltr';
our $RTL = 'rtl';
our $LEFT = 'left';
our $RIGHT = 'right';

# This is a lookbehind match to ensure that we don't replace instances
# of our string token (left, rtl, etc...) if there's a letter in front of it.
# Specifically, this prevents replacements like 'background: url(bright.png)'.
our $LOOKBEHIND_NOT_LETTER = q'(?<![a-zA-Z])';

# This is a lookahead match to make sure we don't replace left and right
# in actual classnames, so that we don't break the HTML/CSS dependencies.
# Read literally, it says ignore cases where the word left, for instance, is
# directly followed by valid classname characters and a curly brace.
# ex: .column-left {float: left} will become .column-left {float: right}
our $LOOKAHEAD_NOT_OPEN_BRACE = sprintf( q'(?!(?:%s|%s|%s|#|\:|\.|\,|\+|>)*?{)',
                            $NMCHAR, $TOKEN_LINES, $SPACE );

# These two lookaheads are to test whether or not we are within a
# background: url(HERE) situation.
# Ref: http://www.w3.org/TR/CSS21/syndata.html#uri
our $VALID_AFTER_URI_CHARS = sprintf( q'[\'\"]?%s', $WHITESPACE );
our $LOOKAHEAD_NOT_CLOSING_PAREN = sprintf( q'(?!%s?%s\))', $URL_CHARS,
                                                $VALID_AFTER_URI_CHARS );
our $LOOKAHEAD_FOR_CLOSING_PAREN = sprintf( q'(?=%s?%s\))', $URL_CHARS,
                                                $VALID_AFTER_URI_CHARS );

# Compile a regex to swap left and right values in 4 part notations.
# We need to match negatives and decimal numeric values.
# The case of border-radius is extra complex, so we handle it separately below.
# ex. 'margin: .25em -2px 3px 0' becomes 'margin: .25em 0 3px -2px'.

our $POSSIBLY_NEGATIVE_QUANTITY = sprintf( q'((?:-?%s)|(?:inherit|auto))', $QUANTITY );
our $POSSIBLY_NEGATIVE_QUANTITY_SPACE = sprintf( q'%s%s%s', $POSSIBLY_NEGATIVE_QUANTITY,
                                                $SPACE,
                                                $WHITESPACE );

our $FOUR_NOTATION_QUANTITY_RE = risprintf( q'%s%s%s%s',
                                       $POSSIBLY_NEGATIVE_QUANTITY_SPACE,
                                       $POSSIBLY_NEGATIVE_QUANTITY_SPACE,
                                       $POSSIBLY_NEGATIVE_QUANTITY_SPACE,
                                       $POSSIBLY_NEGATIVE_QUANTITY );
our $COLOR = sprintf( q'(%s|%s)', $NAME, $HASH );
our $COLOR_SPACE = sprintf( q'%s%s', $COLOR, $SPACE );
our $FOUR_NOTATION_COLOR_RE = risprintf( q'(-color%s:%s)%s%s%s(%s)',
                                    $WHITESPACE,
                                    $WHITESPACE,
                                    $COLOR_SPACE,
                                    $COLOR_SPACE,
                                    $COLOR_SPACE,
                                    $COLOR );

# border-radius is very different from usual 4 part notation: ABCD should
# change to BADC (while it would be ADCB in normal 4 part notation), ABC
# should change to BABC, and AB should change to BA
our $BORDER_RADIUS_RE = risprintf( q'((?:%s)?)border-radius(%s:%s)' .
                               '(?:%s)?(?:%s)?(?:%s)?(?:%s)' .
                               '(?:%s/%s(?:%s)?(?:%s)?(?:%s)?(?:%s))?', $IDENT,
                                                                          $WHITESPACE,
                                                                          $WHITESPACE,
                                                                          $POSSIBLY_NEGATIVE_QUANTITY_SPACE,
                                                                          $POSSIBLY_NEGATIVE_QUANTITY_SPACE,
                                                                          $POSSIBLY_NEGATIVE_QUANTITY_SPACE,
                                                                          $POSSIBLY_NEGATIVE_QUANTITY,
                                                                          $WHITESPACE,
                                                                          $WHITESPACE,
                                                                          $POSSIBLY_NEGATIVE_QUANTITY_SPACE,
                                                                          $POSSIBLY_NEGATIVE_QUANTITY_SPACE,
                                                                          $POSSIBLY_NEGATIVE_QUANTITY_SPACE,
                                                                          $POSSIBLY_NEGATIVE_QUANTITY );



# Compile the cursor resize regexes
our $CURSOR_EAST_RE = resprintf( $LOOKBEHIND_NOT_LETTER . '([ns]?)e-resize' );
our $CURSOR_WEST_RE = resprintf( $LOOKBEHIND_NOT_LETTER . '([ns]?)w-resize' );

# Matches the condition where we need to replace the horizontal component
# of a background-position value when expressed in horizontal percentage.
# Had to make two regexes because in the case of position-x there is only
# one quantity, and otherwise we don't want to match and change cases with only
# one quantity.
our $BG_HORIZONTAL_PERCENTAGE_RE = resprintf( q'background(-position)?(%s:%s)' .
                                         q'([^%%]*?)(%s)%%' .
                                         q'(%s(?:%s|top|center|bottom))', $WHITESPACE,
                                                                           $WHITESPACE,
                                                                           $NUM,
                                                                           $WHITESPACE,
                                                                           $POSSIBLY_NEGATIVE_QUANTITY );

our $BG_HORIZONTAL_PERCENTAGE_X_RE = resprintf( q'background-position-x(%s:%s)' .
                                           q'(%s)%%', $WHITESPACE,
                                                       $WHITESPACE,
                                                       $NUM );
# Non-percentage units used for CSS lengths
our $LENGTH_UNIT = q'(?:em|ex|px|cm|mm|in|pt|pc)';
# To make sure the lone 0 is not just starting a number (like "02") or a percentage like ("0 %")
our $LOOKAHEAD_END_OF_ZERO = sprintf( '(?![0-9]|%s%%)', $WHITESPACE );
# A length with a unit specified. Matches "0" too, as it's a length, not a percentage.
our $LENGTH = sprintf( '(?:-?%s(?:%s%s)|0+%s)', $NUM,
                                    $WHITESPACE,
                                    $LENGTH_UNIT,
                                    $LOOKAHEAD_END_OF_ZERO );

# Zero length. Used in the replacement functions.
our $ZERO_LENGTH = resprintf( q'(?:-?0+(?:%s%s)|0+%s)$', $WHITESPACE,
                                                      $LENGTH_UNIT,
                                                      $LOOKAHEAD_END_OF_ZERO );

# Matches background, background-position, and background-position-x
# properties when using a CSS length for its horizontal positioning.
our $BG_HORIZONTAL_LENGTH_RE = resprintf( q'background(-position)?(%s:%s)' .
                                      q'((?:.+?%s+)??)(%s)' .
                                      q'((?:%s+)(?:%s|top|center|bottom))', $WHITESPACE,
                                                                            $WHITESPACE,
                                                                            $SPACE,
                                                                            $LENGTH,
                                                                            $SPACE,
                                                                            $POSSIBLY_NEGATIVE_QUANTITY );

our $BG_HORIZONTAL_LENGTH_X_RE = resprintf( q'background-position-x(%s:%s)' .
                                        q'(%s)', $WHITESPACE,
                                                  $WHITESPACE,
                                                  $LENGTH );

# Matches the opening of a body selector.
our $BODY_SELECTOR = sprintf( q'body%s{%s', $WHITESPACE, $WHITESPACE );

# Matches anything up until the closing of a selector.
our $CHARS_WITHIN_SELECTOR = q'[^\}]*?';

# Matches the direction property in a selector.
our $DIRECTION_RE = sprintf( q'direction%s:%s', $WHITESPACE, $WHITESPACE );



sub resprintf {
    my $fmt = shift;
    my $ret = sprintf( $fmt, @_ );

    return qr/$ret/;
}

sub risprintf {
    my $fmt = shift;
    my $ret = sprintf( $fmt, @_ );

    return qr/$ret/i;
}

# These allow us to swap "ltr" with "rtl" and vice versa ONLY within the
# body selector and on the same line.
our $BODY_DIRECTION_LTR_RE = risprintf( q'(%s)(%s)(%s)(ltr)',
                                   $BODY_SELECTOR, $CHARS_WITHIN_SELECTOR,
                                    $DIRECTION_RE );
our $BODY_DIRECTION_RTL_RE = risprintf( q'(%s)(%s)(%s)(rtl)',
                                   $BODY_SELECTOR, $CHARS_WITHIN_SELECTOR,
                                    $DIRECTION_RE );

# Allows us to swap "direction:ltr" with "direction:rtl" and
# vice versa anywhere in a line.
our $DIRECTION_LTR_RE = resprintf( q'%s(ltr)', $DIRECTION_RE );
our $DIRECTION_RTL_RE = resprintf( q'%s(rtl)', $DIRECTION_RE );

# We want to be able to switch left with right and vice versa anywhere
# we encounter left/right strings, EXCEPT inside the background:url(). The next
# two regexes are for that purpose. We have alternate IN_URL versions of the
# regexes compiled in case the user passes the flag that they do
# actually want to have left and right swapped inside of background:urls.
our $LEFT_RE = risprintf( '%s((?:top|bottom)?)(%s)%s%s', $LOOKBEHIND_NOT_LETTER,
                                                      $LEFT,
                                                      $LOOKAHEAD_NOT_CLOSING_PAREN,
                                                      $LOOKAHEAD_NOT_OPEN_BRACE );
our $RIGHT_RE = risprintf( '%s((?:top|bottom)?)(%s)%s%s', $LOOKBEHIND_NOT_LETTER,
                                                       $RIGHT,
                                                       $LOOKAHEAD_NOT_CLOSING_PAREN,
                                                       $LOOKAHEAD_NOT_OPEN_BRACE );
our $LEFT_IN_URL_RE = risprintf( '%s(%s)%s', $LOOKBEHIND_NOT_LETTER,
                                          $LEFT,
                                          $LOOKAHEAD_FOR_CLOSING_PAREN );
our $RIGHT_IN_URL_RE = risprintf( '%s(%s)%s', $LOOKBEHIND_NOT_LETTER,
                                           $RIGHT,
                                           $LOOKAHEAD_FOR_CLOSING_PAREN );
our $LTR_IN_URL_RE = risprintf( '%s(%s)%s', $LOOKBEHIND_NOT_LETTER,
                                         $LTR,
                                         $LOOKAHEAD_FOR_CLOSING_PAREN );
our $RTL_IN_URL_RE = risprintf( '%s(%s)%s', $LOOKBEHIND_NOT_LETTER,
                                         $RTL,
                                         $LOOKAHEAD_FOR_CLOSING_PAREN );

our $COMMENT_RE = risprintf( '(%s)', $COMMENT );

our $NOFLIP_TOKEN = q'@noflip';
# The NOFLIP_TOKEN inside of a comment. For now, this requires that comments
# be in the input, which means users of a css compiler would have to run
# this script first if they want this functionality.
our $NOFLIP_ANNOTATION = resprintf( q'/\*!?%s%s%s\*/', $WHITESPACE,
                                       $NOFLIP_TOKEN,
                                       $WHITESPACE );

# After a NOFLIP_ANNOTATION, and within a class selector, we want to be able
# to set aside a single rule not to be flipped. We can do this by matching
# our NOFLIP annotation and then using a lookahead to make sure there is not
# an opening brace before the match.
our $NOFLIP_SINGLE_RE = risprintf( q'(%s%s[^;}]+;?)', $NOFLIP_ANNOTATION,
                                                   $LOOKAHEAD_NOT_OPEN_BRACE );

# After a NOFLIP_ANNOTATION, we want to grab anything up until the next } which
# means the entire following class block. This will prevent all of its
# declarations from being flipped.
our $NOFLIP_CLASS_RE = risprintf( q'(%s%s})', $NOFLIP_ANNOTATION,
                                           $CHARS_WITHIN_SELECTOR );

# border-radis properties and their values
our $BORDER_RADIUS_TOKENIZER_RE = risprintf( q'((?:%s)?border-radius%s:[^;}]+;?)', $IDENT,
                                                                                $WHITESPACE );

our $GRADIENT_RE = qr/ (
	$CSS::Orientation::IDENT
	[\.-] gradient
	$CSS::Orientation::WHITESPACE
	\( (?: (?>[^()]+) | \([^()]*\) )+ \)
) /ix;

sub FixBodyDirectionLtrAndRtl {
    my ( $line ) = @_;

    $line =~ s!$BODY_DIRECTION_LTR_RE!$1$2$3$TMP_TOKEN!gms;
    $line =~ s!$BODY_DIRECTION_RTL_RE!$1$2$3$LTR!gms;
    $line =~ s!$TMP_TOKEN!$RTL!gms;

    return $line;
}

sub FixLeftAndRight {
    my ( $line ) = @_;

    $line =~ s!$LEFT_RE!$1$TMP_TOKEN!gms;
    $line =~ s!$RIGHT_RE!$1$LEFT!gms;
    $line =~ s!$TMP_TOKEN!$RIGHT!gms;

    return $line;
}

sub FixLeftAndRightInUrl {
    my ( $line ) = @_;

    $line =~ s!$LEFT_IN_URL_RE!$TMP_TOKEN!gms;
    $line =~ s!$RIGHT_IN_URL_RE!$LEFT!gms;
    $line =~ s!$TMP_TOKEN!$RIGHT!gms;

    return $line;
}

sub FixLtrAndRtlInUrl {
    my ( $line ) = @_;

    $line =~ s!$LTR_IN_URL_RE!$TMP_TOKEN!gms;
    $line =~ s!$RTL_IN_URL_RE!$LTR!gms;
    $line =~ s!$TMP_TOKEN!$RTL!gms;

    return $line;
}

sub FixCursorProperties {
    my ( $line ) = @_;

    $line =~ s!$CURSOR_EAST_RE!$1$TMP_TOKEN!gms;
    $line =~ s!$CURSOR_WEST_RE!${1}e-resize!gms;
    $line =~ s!$TMP_TOKEN!w-resize!gms;

    return $line;
}

sub FixBackgroundPosition {
    my ( $line, $ignore_bad_bgp ) = @_;

    # leave full match undef where not needed
    $line =~ s!$BG_HORIZONTAL_PERCENTAGE_RE!CalculateNewBackgroundPosition(undef,$1,$2,$3,$4,$5,$6)!egms;
    $line =~ s!$BG_HORIZONTAL_PERCENTAGE_X_RE!CalculateNewBackgroundPositionX(undef,$1,$2)!egms;

    $line =~ s!($BG_HORIZONTAL_LENGTH_RE)!
        defined( $_ = CalculateNewBackgroundLengthPosition( $1, $2, $3, $4, $5, $6, $7, $ignore_bad_bgp ) )
        ? $_
        : return undef
    !egmsx;

    $line =~ s!($BG_HORIZONTAL_LENGTH_X_RE)!
        defined( $_ = CalculateNewBackgroundLengthPositionX( $1, $2, $3, $ignore_bad_bgp ) )
        ? $_
        : return undef
    !egmsx;

    return $line;
}

sub ReorderBorderRadiusPart {
    my @part = grep defined, @_;

    if ( @part == 4 ) {
        return join( ' ', @part[ 1, 0, 3, 2 ] );
    }
    elsif ( @part == 3 ) {
        return join( ' ', @part[ 1, 0, 1, 2 ] );
    }
    elsif ( @part == 2 ) {
        return join( ' ', @part[ 1, 0, ] );
    }
    elsif ( @part == 1 ) {
        return $part[ 0 ];
    }
    else {
        return '';
    }
}

sub ReorderBorderRadius {
    my @m = @_;

    my $first_group = ReorderBorderRadiusPart( @m[ 3 .. 6 ] );
    my $second_group = ReorderBorderRadiusPart( @m[ 7 .. $#m ] );

    if ( $second_group eq '' ) {
        return sprintf( '%sborder-radius%s%s', $m[1], $m[2], $first_group );
    }
    else {
        return sprintf( '%sborder-radius%s%s / %s', $m[1], $m[2], $first_group, $second_group );
    }
}

sub CalculateNewBackgroundPosition {
    my @m = @_;

    my $new_x = 100 - $m[4];
    my $position_string = defined( $m[1] ) ? $m[1] : '';

    return sprintf( 'background%s%s%s%s%%%s', $position_string, $m[2], $m[3], $new_x, $m[5] );
}

sub CalculateNewBackgroundPositionX {
    my @m = @_;

    my $new_x = 100 - $m[2];

    return sprintf( 'background-position-x%s%s%%', $m[1], $new_x );
}

sub CalculateNewBackgroundLengthPosition {
    my $ignore_bad_bgp = @_ > 7 ? pop( @_ ) : 0;
    my @m = @_;

    unless ( $m[4] =~ $ZERO_LENGTH ) {
        warn( "Unmirrorable horizontal value $m[4]: $m[0]" );
        return $ignore_bad_bgp ? $m[0] : undef;
    }

    my $position_string = defined( $m[1] ) ? $m[1] : '';

    return sprintf( 'background%s%s%s100%%%s', $position_string, $m[2], $m[3], $m[5] );

}

sub CalculateNewBackgroundLengthPositionX {
    my $ignore_bad_bgp = @_ > 3 ? pop( @_ ) : 0;
    my @m = @_;

    unless ( $m[2] =~ $ZERO_LENGTH ) {
        warn( "Unmirrorable horizontal value $m[2]: $m[0]" );
        return $ignore_bad_bgp ? $m[0] : undef;
    }

    return sprintf( 'background-position-x%s100%%', $m[1] );
}

sub FixBorderRadius {
    my ( $line ) = @_;

    # full match not needed, leave undef
    $line =~ s!$BORDER_RADIUS_RE!ReorderBorderRadius(undef,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10)!egms;

    return $line;
}

sub FixFourPartNotation {
    my $line = shift;

    $line =~ s!$FOUR_NOTATION_QUANTITY_RE!$1 $4 $3 $2!g;
    $line =~ s!$FOUR_NOTATION_COLOR_RE!$1$2 $5 $4 $3!g;

    return $line;
}

sub ChangeLeftToRightToLeft {
    my ( $lines, $swap_ltr_rtl_in_url, $swap_left_right_in_url, $ignore_bad_bgp ) = @_;

    my $line = join( $TOKEN_LINES, ref( $lines ) ? @$lines : $lines );

    # Tokenize any single line rules with the /* noflip */ annotation.
    my $noflip_single_tokenizer = CSS::Orientation::Tokenizer->new( $NOFLIP_SINGLE_RE, 'NOFLIP_SINGLE' );
    $line = $noflip_single_tokenizer->tokenize( $line );

    # Tokenize any class rules with the /* noflip */ annotation.
    my $noflip_class_tokenizer = CSS::Orientation::Tokenizer->new( $NOFLIP_CLASS_RE, 'NOFLIP_CLASS' );
    $line = $noflip_class_tokenizer->tokenize( $line );

    # Tokenize the comments so we can preserve them through the changes.
    my $comment_tokenizer = CSS::Orientation::Tokenizer->new( $COMMENT_RE, 'C' );
    $line = $comment_tokenizer->tokenize( $line );

    # Tokenize gradients since we don't want to mirror the values inside
    my $gradient_tokenizer = CSS::Orientation::Tokenizer->new( $GRADIENT_RE, 'GRADIENT' );
    $line = $gradient_tokenizer->tokenize( $line );

    # Here starteth the various left/right orientation fixes.
    $line = FixBodyDirectionLtrAndRtl( $line );

    if ( $swap_left_right_in_url ) {
        $line = FixLeftAndRightInUrl( $line );
    }

    if ( $swap_ltr_rtl_in_url ) {
        $line = FixLtrAndRtlInUrl( $line );
    }

    $line = FixLeftAndRight( $line );
    $line = FixCursorProperties( $line );

    $line = FixBorderRadius( $line );
    # Since FourPartNotation conflicts with BorderRadius, we tokenize border-radius properties here.
    my $border_radius_tokenizer = CSS::Orientation::Tokenizer->new( $BORDER_RADIUS_TOKENIZER_RE, 'BORDER_RADIUS' );
    $line = $border_radius_tokenizer->tokenize( $line );
    $line = FixFourPartNotation( $line );
    $line = $border_radius_tokenizer->detokenize( $line );

    $line = FixBackgroundPosition( $line, $ignore_bad_bgp );

    unless ( defined( $line ) ) {
        return undef;
    }

    $line = $gradient_tokenizer->detokenize( $line );

    # DeTokenize the single line noflips.
    $line = $noflip_single_tokenizer->detokenize( $line );

    # DeTokenize the class-level noflips.
    $line = $noflip_class_tokenizer->detokenize( $line );

    # DeTokenize the comments.
    $line = $comment_tokenizer->detokenize( $line );

    # Rejoin the lines back together.
    my @lines = split( $TOKEN_LINES, $line );

    return ref( $lines ) ? \@lines : $lines[0];
}

sub change {
    shift;
    ChangeLeftToRightToLeft( @_ );
}

1;

package CSS::Orientation::Tokenizer;

use strict;
use warnings;

sub new {
    my ( $class, $re, $string ) = @_;
    my $self = bless( {
        're'        => $re,
        'string'    => $string,
        'originals' => [],
    }, $class );

    return $self;
}

sub tokenize {
    my ( $self, $line ) = @_;

    $line =~ s!$self->{re}!
        $CSS::Orientation::TOKEN_DELIMITER .
        $self->{string} . '_' .
        push( @{ $self->{originals} }, $1 ) .
        $CSS::Orientation::TOKEN_DELIMITER
    !egx;

    return $line;
}

sub detokenize {
    my ( $self, $line ) = @_;

    $line =~ s!
        $CSS::Orientation::TOKEN_DELIMITER
        $self->{string} _
        ([0-9]+)
        $CSS::Orientation::TOKEN_DELIMITER
    !
        $1 > 0 && $1 <= @{ $self->{originals} } ? $self->{originals}[$1-1] : ''
    !egx;

    return $line;
}

1;

__END__

=head1 NAME

CSS::Orientation - Perl extension to change text orientation in CSS

=head1 SYNOPSIS

  use CSS::Orientation qw( ChangeLeftToRightToLeft );
  ...
  $rtlcss = ChangeLeftToRightToLeft( $css );
  ...
  # or without imports
  $rtlcss = CSS::Orientation->change( $css );

=head1 DESCRIPTION

This module flips CSS from LTR to an RTL orienation and vice-versa.
It is a port of cssjanus to Perl to have the same functionality available
without having to start another application.

=head2 EXPORT

Nothing by default.

=head3 ChangeLeftToRightToLeft( \@lines | $line [ swap_ltr_rtl_in_url swap_left_right_in_url ] )

=head2 METHODS

To use it without import or after require:

=head3 change( \@lines | $line [ swap_ltr_rtl_in_url swap_left_right_in_url ] )

=back

=head1 SEE ALSO

L<http://code.google.com/p/cssjanus/>

=head1 AUTHOR

Simon Bertrang, E<lt>janus@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2013 by Simon Bertrang

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.12.2 or,
at your option, any later version of Perl 5 you may have available.

=cut

# vim: ts=4 sw=4 et: