| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | =encoding utf8 | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | =head1 NAME | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | IRI - Internationalized Resource Identifiers | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | =head1 VERSION | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | This document describes IRI version 0.010 | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | use IRI; | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | my $i = IRI->new(value => 'https://example.org:80/index#frag'); | 
| 16 |  |  |  |  |  |  | say $i->scheme; # 'https' | 
| 17 |  |  |  |  |  |  | say $i->path; # '/index' | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | my $base = IRI->new(value => "http://www.hestebedg\x{e5}rd.dk/"); | 
| 20 |  |  |  |  |  |  | my $i = IRI->new(value => '#frag', base => $base); | 
| 21 |  |  |  |  |  |  | say $i->abs; # 'http://www.hestebedgÄrd.dk/#frag' | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | # Defer parsing of the IRI until necessary | 
| 24 |  |  |  |  |  |  | my $i = IRI->new(value => "http://www.hestebedg\x{e5}rd.dk/", lazy => 1); | 
| 25 |  |  |  |  |  |  | say $i->path; # path is parsed here | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | The IRI module provides an object representation for Internationalized | 
| 30 |  |  |  |  |  |  | Resource Identifiers (IRIs) as defined by | 
| 31 |  |  |  |  |  |  | L and supports their parsing, | 
| 32 |  |  |  |  |  |  | serializing, and base resolution. | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | =head1 ATTRIBUTES | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | =over 4 | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | =item C<< lazy >> | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | A boolean value indicating whether the IRI should be parsed (and validated) | 
| 41 |  |  |  |  |  |  | during object construction (false), or parsed only when an IRI component is | 
| 42 |  |  |  |  |  |  | accessed (true). If no components are ever needed (e.g. an IRI is constructed | 
| 43 |  |  |  |  |  |  | with a C<< value >> and C<< value >> is the only accessor ever called), no | 
| 44 |  |  |  |  |  |  | parsing will take place. | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | =back | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | =head1 METHODS | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | =over 4 | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | =item C<< as_string >> | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | Returns the absolute IRI string resolved against the base IRI, if present; | 
| 55 |  |  |  |  |  |  | the relative IRI string otherwise. | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | =item C<< abs >> | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | Returns the absolute IRI string (resolved against the base IRI if present). | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | =item C<< scheme >> | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | =item C<< host >> | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | =item C<< port >> | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | =item C<< user >> | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | =item C<< path >> | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | =item C<< fragment >> | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | =item C<< query >> | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | Returns the respective component of the parsed IRI. | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | =cut | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | { | 
| 80 |  |  |  |  |  |  | package IRI; | 
| 81 | 3 |  |  | 3 |  | 2234 | use v5.10.1; | 
|  | 3 |  |  |  |  | 11 |  | 
| 82 | 3 |  |  | 3 |  | 14 | use warnings; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 109 |  | 
| 83 |  |  |  |  |  |  | our $VERSION	= '0.010'; | 
| 84 | 3 |  |  | 3 |  | 1697 | use Moo; | 
|  | 3 |  |  |  |  | 32343 |  | 
|  | 3 |  |  |  |  | 14 |  | 
| 85 | 3 |  |  | 3 |  | 5814 | use MooX::HandlesVia; | 
|  | 3 |  |  |  |  | 28748 |  | 
|  | 3 |  |  |  |  | 17 |  | 
| 86 | 3 |  |  | 3 |  | 2164 | use Types::Standard qw(Str InstanceOf HashRef Bool); | 
|  | 3 |  |  |  |  | 221829 |  | 
|  | 3 |  |  |  |  | 30 |  | 
| 87 | 3 |  |  | 3 |  | 3486 | use Scalar::Util qw(blessed); | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 3597 |  | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | # 	class_type 'URI'; | 
| 90 |  |  |  |  |  |  | # 	coerce 'IRI' => from 'Str' => via { IRI->new( value => $_ ) }; | 
| 91 |  |  |  |  |  |  | # 	coerce 'IRI' => from 'URI' => via { IRI->new( value => $_->as_string ) }; | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | has 'lazy' => (is => 'ro', isa => Bool, default => 0); | 
| 94 |  |  |  |  |  |  | has '_initialized' => (is => 'rw', isa => Bool, default => 0, init_arg => undef); | 
| 95 |  |  |  |  |  |  | has 'base' => (is => 'ro', isa => InstanceOf['IRI'], predicate => 'has_base', coerce => sub { | 
| 96 |  |  |  |  |  |  | my $base	= shift; | 
| 97 |  |  |  |  |  |  | if (blessed($base)) { | 
| 98 |  |  |  |  |  |  | if ($base->isa('IRI')) { | 
| 99 |  |  |  |  |  |  | return $base; | 
| 100 |  |  |  |  |  |  | } elsif ($base->isa('URI')) { | 
| 101 |  |  |  |  |  |  | return IRI->new( value => $base->as_string ); | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  | } else { | 
| 104 |  |  |  |  |  |  | return IRI->new($base); | 
| 105 |  |  |  |  |  |  | } | 
| 106 |  |  |  |  |  |  | }); | 
| 107 |  |  |  |  |  |  | has 'value' => (is => 'ro', isa => Str, default => ''); | 
| 108 |  |  |  |  |  |  | has 'components' => (is => 'ro', writer => '_set_components'); | 
| 109 |  |  |  |  |  |  | has 'abs' => (is => 'ro', lazy => 1, builder => '_abs'); | 
| 110 |  |  |  |  |  |  | has 'resolved_components' => ( | 
| 111 |  |  |  |  |  |  | is		=> 'ro', | 
| 112 |  |  |  |  |  |  | isa		=> HashRef, | 
| 113 |  |  |  |  |  |  | lazy	=> 1, | 
| 114 |  |  |  |  |  |  | builder	=> '_resolved_components', | 
| 115 |  |  |  |  |  |  | handles_via	=> 'Hash', | 
| 116 |  |  |  |  |  |  | handles	=> { | 
| 117 |  |  |  |  |  |  | scheme		=>  [ accessor => 'scheme' ], | 
| 118 |  |  |  |  |  |  | host		=>  [ accessor => 'host' ], | 
| 119 |  |  |  |  |  |  | port		=>  [ accessor => 'port' ], | 
| 120 |  |  |  |  |  |  | user		=>  [ accessor => 'user' ], | 
| 121 |  |  |  |  |  |  | path		=>  [ accessor => 'path' ], | 
| 122 |  |  |  |  |  |  | fragment	=>  [ accessor => 'fragment' ], | 
| 123 |  |  |  |  |  |  | query		=>  [ accessor => 'query' ], | 
| 124 |  |  |  |  |  |  | }, | 
| 125 |  |  |  |  |  |  | ); | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | around BUILDARGS => sub { | 
| 128 |  |  |  |  |  |  | my $orig 	= shift; | 
| 129 |  |  |  |  |  |  | my $class	= shift; | 
| 130 |  |  |  |  |  |  | if (scalar(@_) == 1) { | 
| 131 |  |  |  |  |  |  | return $class->$orig(value => shift); | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  | return $class->$orig(@_); | 
| 134 |  |  |  |  |  |  | }; | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | sub BUILD { | 
| 137 | 24 |  |  | 24 | 0 | 1909 | my $self	= shift; | 
| 138 | 24 | 50 |  |  |  | 94 | unless ($self->lazy) { | 
| 139 | 24 |  |  |  |  | 65 | my $comp	= $self->_parse_components($self->value); | 
| 140 |  |  |  |  |  |  | } | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | before [qw(components as_string abs resolved_components scheme host port user path fragment query)] => sub { | 
| 144 |  |  |  |  |  |  | my $self	= shift; | 
| 145 |  |  |  |  |  |  | if (not $self->_initialized) { | 
| 146 |  |  |  |  |  |  | # 			warn "Lazily initializing IRI"; | 
| 147 |  |  |  |  |  |  | my $comp	= $self->_parse_components($self->value); | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  | }; | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | # These regexes are (mostly) from the syntax grammar in RFC 3987 | 
| 152 |  |  |  |  |  |  | my $HEXDIG			= qr<[0-9A-F]>o; | 
| 153 |  |  |  |  |  |  | my $ALPHA			= qr<[A-Za-z]>o; | 
| 154 |  |  |  |  |  |  | my $subdelims		= qr<[!\$&'()*+,;=]>xo; | 
| 155 |  |  |  |  |  |  | my $gendelims		= qr<[":/?#@] | \[ | \]>xo; | 
| 156 |  |  |  |  |  |  | my $reserved		= qr<${gendelims} | ${subdelims}>o; | 
| 157 |  |  |  |  |  |  | my $unreserved		= qr<${ALPHA} | [0-9] | [-._~]>xo; | 
| 158 |  |  |  |  |  |  | my $pctencoded		= qr<%[0-9A-Fa-f]{2}>o; | 
| 159 |  |  |  |  |  |  | my $decoctet		= qr< | 
| 160 |  |  |  |  |  |  | [0-9]			# 0-9 | 
| 161 |  |  |  |  |  |  | |	[1-9][0-9]		# 10-99 | 
| 162 |  |  |  |  |  |  | |	1 [0-9]{2}		# 100-199 | 
| 163 |  |  |  |  |  |  | |	2 [0-4] [0-9]	# 200-249 | 
| 164 |  |  |  |  |  |  | |	25 [0-5]		# 250-255 | 
| 165 |  |  |  |  |  |  | >xo; | 
| 166 |  |  |  |  |  |  | my $IPv4address		= qr< | 
| 167 |  |  |  |  |  |  | # IPv4address | 
| 168 |  |  |  |  |  |  | ${decoctet}[.]${decoctet}[.]${decoctet}[.]${decoctet} | 
| 169 |  |  |  |  |  |  | >xo; | 
| 170 |  |  |  |  |  |  | my $h16				= qr<${HEXDIG}{1,4}>o; | 
| 171 |  |  |  |  |  |  | my $ls32			= qr< | 
| 172 |  |  |  |  |  |  | ( ${h16} : ${h16} ) | 
| 173 |  |  |  |  |  |  | |	${IPv4address} | 
| 174 |  |  |  |  |  |  | >xo; | 
| 175 |  |  |  |  |  |  | my $IPv6address		= qr< | 
| 176 |  |  |  |  |  |  | # IPv6address | 
| 177 |  |  |  |  |  |  | (								 ( ${h16} : ){6} ${ls32}) | 
| 178 |  |  |  |  |  |  | | (							  :: ( ${h16} : ){5} ${ls32}) | 
| 179 |  |  |  |  |  |  | | ((					${h16} )? :: ( ${h16} : ){4} ${ls32}) | 
| 180 |  |  |  |  |  |  | | (( ( ${h16} : ){0,1} ${h16} )? :: ( ${h16} : ){3} ${ls32}) | 
| 181 |  |  |  |  |  |  | | (( ( ${h16} : ){0,2} ${h16} )? :: ( ${h16} : ){2} ${ls32}) | 
| 182 |  |  |  |  |  |  | | (( ( ${h16} : ){0,3} ${h16} )? ::   ${h16} :		 ${ls32}) | 
| 183 |  |  |  |  |  |  | | (( ( ${h16} : ){0,4} ${h16} )? ::				 ${ls32}) | 
| 184 |  |  |  |  |  |  | | (( ( ${h16} : ){0,5} ${h16} )? ::				 ${h16}) | 
| 185 |  |  |  |  |  |  | | (( ( ${h16} : ){0,6} ${h16} )? ::) | 
| 186 |  |  |  |  |  |  | >xo; | 
| 187 |  |  |  |  |  |  | my $IPvFuture		= qrxo; | 
| 188 |  |  |  |  |  |  | my $IPliteral		= qr<\[ | 
| 189 |  |  |  |  |  |  | # IPliteral | 
| 190 |  |  |  |  |  |  | (${IPv6address} | ${IPvFuture}) | 
| 191 |  |  |  |  |  |  | \] | 
| 192 |  |  |  |  |  |  | >xo; | 
| 193 |  |  |  |  |  |  | my $port			= qr<(?[0-9]*)>o; | 
| 194 |  |  |  |  |  |  | my $scheme			= qr<(?${ALPHA} ( ${ALPHA} | [0-9] | [+] | [-] | [.] )*)>xo; | 
| 195 |  |  |  |  |  |  | my $iprivate		= qr<[\x{E000}-\x{F8FF}] | [\x{F0000}-\x{FFFFD}] | [\x{100000}-\x{10FFFD}]>xo; | 
| 196 |  |  |  |  |  |  | my $ucschar			= qr< | 
| 197 |  |  |  |  |  |  | [\x{a0}-\x{d7ff}] | [\x{f900}-\x{fdcf}] | [\x{fdf0}-\x{ffef}] | 
| 198 |  |  |  |  |  |  | |	[\x{10000}-\x{1FFFD}] | [\x{20000}-\x{2FFFD}] | [\x{30000}-\x{3FFFD}] | 
| 199 |  |  |  |  |  |  | |	[\x{40000}-\x{4FFFD}] | [\x{50000}-\x{5FFFD}] | [\x{60000}-\x{6FFFD}] | 
| 200 |  |  |  |  |  |  | |	[\x{70000}-\x{7FFFD}] | [\x{80000}-\x{8FFFD}] | [\x{90000}-\x{9FFFD}] | 
| 201 |  |  |  |  |  |  | |	[\x{A0000}-\x{AFFFD}] | [\x{B0000}-\x{BFFFD}] | [\x{C0000}-\x{CFFFD}] | 
| 202 |  |  |  |  |  |  | |	[\x{D0000}-\x{DFFFD}] | [\x{E1000}-\x{EFFFD}] | 
| 203 |  |  |  |  |  |  | >xo; | 
| 204 |  |  |  |  |  |  | my $iunreserved		= qr<${ALPHA}|[0-9]|[-._~]|${ucschar}>o; | 
| 205 |  |  |  |  |  |  | my $ipchar			= qr<(${iunreserved})|(${pctencoded})|(${subdelims})|:|@>o; | 
| 206 |  |  |  |  |  |  | my $ifragment		= qr<(?(${ipchar}|/|[?])*)>o; | 
| 207 |  |  |  |  |  |  | my $iquery			= qr<(?(${ipchar}|${iprivate}|/|[?])*)>o; | 
| 208 |  |  |  |  |  |  | my $isegmentnznc	= qr<(${iunreserved}|${pctencoded}|${subdelims}|@)+ # non-zero-length segment without any colon ":" | 
| 209 |  |  |  |  |  |  | >xo; | 
| 210 |  |  |  |  |  |  | my $isegmentnz		= qr<${ipchar}+>o; | 
| 211 |  |  |  |  |  |  | my $isegment		= qr<${ipchar}*>o; | 
| 212 |  |  |  |  |  |  | my $ipathempty		= qr<>o; | 
| 213 |  |  |  |  |  |  | my $ipathrootless	= qr<(?${isegmentnz}(/${isegment})*)>o; | 
| 214 |  |  |  |  |  |  | my $ipathnoscheme	= qr<(?${isegmentnznc}(/${isegment})*)>o; | 
| 215 |  |  |  |  |  |  | my $ipathabsolute	= qr<(?/(${isegmentnz}(/${isegment})*)?)>o; | 
| 216 |  |  |  |  |  |  | my $ipathabempty	= qr<(?(/${isegment})*)>o; | 
| 217 |  |  |  |  |  |  | my $ipath			= qr< | 
| 218 |  |  |  |  |  |  | ${ipathabempty}		# begins with "/" or is empty | 
| 219 |  |  |  |  |  |  | |	${ipathabsolute}	# begins with "/" but not "//" | 
| 220 |  |  |  |  |  |  | |	${ipathnoscheme}	# begins with a non-colon segment | 
| 221 |  |  |  |  |  |  | |	${ipathrootless}	# begins with a segment | 
| 222 |  |  |  |  |  |  | |	${ipathempty}		# zero characters | 
| 223 |  |  |  |  |  |  | >xo; | 
| 224 |  |  |  |  |  |  | my $iregname		= qr<(${iunreserved}|${pctencoded}|${subdelims})*>o; | 
| 225 |  |  |  |  |  |  | my $ihost			= qr<(?${IPliteral}|${IPv4address}|${iregname})>o; | 
| 226 |  |  |  |  |  |  | my $iuserinfo		= qr<(?(${iunreserved}|${pctencoded}|${subdelims}|:)*)>o; | 
| 227 |  |  |  |  |  |  | my $iauthority		= qr<(${iuserinfo}@)?${ihost}(:${port})?>o; | 
| 228 |  |  |  |  |  |  | my $irelativepart	= qr< | 
| 229 |  |  |  |  |  |  | (//${iauthority}${ipathabempty}) | 
| 230 |  |  |  |  |  |  | |	${ipathabsolute} | 
| 231 |  |  |  |  |  |  | |	${ipathnoscheme} | 
| 232 |  |  |  |  |  |  | |	${ipathempty} | 
| 233 |  |  |  |  |  |  | >xo; | 
| 234 |  |  |  |  |  |  | my $irelativeref	= qr<${irelativepart}([?]${iquery})?(#${ifragment})?>o; | 
| 235 |  |  |  |  |  |  | my $ihierpart		= qr<(//${iauthority}${ipathabempty})|(${ipathabsolute})|(${ipathrootless})|(${ipathempty})>o; | 
| 236 |  |  |  |  |  |  | my $absoluteIRI		= qr<${scheme}:${ihierpart}([?]${iquery})?>o; | 
| 237 |  |  |  |  |  |  | my $IRI				= qr<${scheme}:${ihierpart}([?]${iquery})?(#${ifragment})?>o; | 
| 238 |  |  |  |  |  |  | my $IRIreference	= qr<${IRI}|${irelativeref}>o; | 
| 239 |  |  |  |  |  |  | sub _parse_components { | 
| 240 | 24 |  |  | 24 |  | 33 | my $self	= shift; | 
| 241 | 24 |  |  |  |  | 33 | my $v		= shift; | 
| 242 | 24 |  |  |  |  | 30 | my $c; | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 24 | 50 |  |  |  | 9853 | if ($v =~ /^${IRIreference}$/o) { | 
| 245 | 3 |  |  | 3 |  | 1506 | %$c = %+; | 
|  | 3 |  |  |  |  | 1095 |  | 
|  | 3 |  |  |  |  | 116 |  | 
|  | 24 |  |  |  |  | 328 |  | 
| 246 |  |  |  |  |  |  | } else { | 
| 247 | 3 |  |  | 3 |  | 22 | use Data::Dumper; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 6071 |  | 
| 248 | 0 |  |  |  |  | 0 | die "Not a valid IRI? " . Dumper($v); | 
| 249 |  |  |  |  |  |  | } | 
| 250 |  |  |  |  |  |  |  | 
| 251 | 24 |  | 100 |  |  | 276 | $c->{path}	//= ''; | 
| 252 | 24 |  |  |  |  | 80 | $self->_set_components($c); | 
| 253 | 24 |  |  |  |  | 518 | $self->_initialized(1); | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | sub _merge { | 
| 257 | 4 |  |  | 4 |  | 8 | my $self	= shift; | 
| 258 | 4 |  |  |  |  | 6 | my $base	= shift; | 
| 259 |  |  |  |  |  |  |  | 
| 260 | 4 |  |  |  |  | 74 | my $bc		= $base->components; | 
| 261 | 4 |  |  |  |  | 215 | my $c		= $self->components; | 
| 262 | 4 |  | 33 |  |  | 127 | my $base_has_authority	= ($bc->{user} or $bc->{port} or defined($bc->{host})); | 
| 263 | 4 | 50 | 33 |  |  | 23 | if ($base_has_authority and not($bc->{path})) { | 
| 264 | 0 |  |  |  |  | 0 | return "/" . $c->{path}; | 
| 265 |  |  |  |  |  |  | } else { | 
| 266 | 4 |  |  |  |  | 12 | my $bp	= $bc->{path}; | 
| 267 | 4 |  |  |  |  | 17 | my @pathParts	= split('/', $bp, -1);	# -1 limit means $path='/' splits into ('', '') | 
| 268 | 4 |  |  |  |  | 8 | pop(@pathParts); | 
| 269 | 4 |  |  |  |  | 9 | push(@pathParts, $c->{path}); | 
| 270 | 4 |  |  |  |  | 16 | my $path	= join('/', @pathParts); | 
| 271 | 4 |  |  |  |  | 12 | return $path; | 
| 272 |  |  |  |  |  |  | } | 
| 273 |  |  |  |  |  |  | } | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | sub _remove_dot_segments { | 
| 276 | 5 |  |  | 5 |  | 9 | my $self	= shift; | 
| 277 | 5 |  |  |  |  | 8 | my $input	= shift; | 
| 278 | 5 |  |  |  |  | 7 | my @output; | 
| 279 | 5 |  |  |  |  | 15 | while (length($input)) { | 
| 280 | 13 | 50 |  |  |  | 80 | if ($input =~ m<^[.][.]/>) { | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 281 | 0 |  |  |  |  | 0 | substr($input, 0, 3)	= ''; | 
| 282 |  |  |  |  |  |  | } elsif ($input =~ m<^[.]/>) { | 
| 283 | 0 |  |  |  |  | 0 | substr($input, 0, 2)	= ''; | 
| 284 |  |  |  |  |  |  | } elsif ($input =~ m<^/[.]/>) { | 
| 285 | 0 |  |  |  |  | 0 | substr($input, 0, 3)	= '/'; | 
| 286 |  |  |  |  |  |  | } elsif ($input eq '/.') { | 
| 287 | 0 |  |  |  |  | 0 | $input	= '/'; | 
| 288 |  |  |  |  |  |  | } elsif ($input =~ m<^/[.][.]/>) { | 
| 289 | 1 |  |  |  |  | 3 | substr($input, 0, 4)	= '/'; | 
| 290 | 1 |  |  |  |  | 3 | pop(@output); | 
| 291 |  |  |  |  |  |  | } elsif ($input eq '/..') { | 
| 292 | 0 |  |  |  |  | 0 | $input	= '/'; | 
| 293 | 0 |  |  |  |  | 0 | pop(@output); | 
| 294 |  |  |  |  |  |  | } elsif ($input eq '.') { | 
| 295 | 0 |  |  |  |  | 0 | $input	= ''; | 
| 296 |  |  |  |  |  |  | } elsif ($input eq '..') { | 
| 297 | 0 |  |  |  |  | 0 | $input	= ''; | 
| 298 |  |  |  |  |  |  | } else { | 
| 299 | 12 |  |  |  |  | 32 | my $leadingSlash	= ($input =~ m<^/>); | 
| 300 | 12 | 50 |  |  |  | 24 | if ($leadingSlash) { | 
| 301 | 12 |  |  |  |  | 28 | substr($input, 0, 1)	= ''; | 
| 302 |  |  |  |  |  |  | } | 
| 303 | 12 |  |  |  |  | 30 | my ($part, @parts)	= split('/', $input, -1); | 
| 304 | 12 |  | 100 |  |  | 34 | $part	//= ''; | 
| 305 | 12 | 100 |  |  |  | 24 | if (scalar(@parts)) { | 
| 306 | 7 |  |  |  |  | 15 | unshift(@parts, ''); | 
| 307 |  |  |  |  |  |  | } | 
| 308 | 12 |  |  |  |  | 27 | $input	= join('/', @parts); | 
| 309 | 12 | 50 |  |  |  | 20 | if ($leadingSlash) { | 
| 310 | 12 |  |  |  |  | 21 | $part	= "/$part"; | 
| 311 |  |  |  |  |  |  | } | 
| 312 | 12 |  |  |  |  | 35 | push(@output, $part); | 
| 313 |  |  |  |  |  |  | } | 
| 314 |  |  |  |  |  |  | } | 
| 315 | 5 |  |  |  |  | 12 | my $newPath = join('', @output); | 
| 316 | 5 |  |  |  |  | 19 | return $newPath; | 
| 317 |  |  |  |  |  |  | } | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | sub _resolved_components { | 
| 320 | 13 |  |  | 13 |  | 609 | my $self	= shift; | 
| 321 | 13 |  |  |  |  | 33 | my $value	= $self->value; | 
| 322 | 13 | 100 | 66 |  |  | 180 | if ($self->has_base and not($self->components->{scheme})) { | 
| 323 |  |  |  |  |  |  | # Resolve IRI relative to the base IRI | 
| 324 | 8 |  |  |  |  | 220 | my $base	= $self->base; | 
| 325 | 8 |  |  |  |  | 14 | my $v		= $self->value; | 
| 326 | 8 |  |  |  |  | 19 | my $bv		= $base->value; | 
| 327 |  |  |  |  |  |  | # 			warn "resolving IRI <$v> relative to the base IRI <$bv>"; | 
| 328 | 8 |  |  |  |  | 10 | my %components	= %{ $self->components }; | 
|  | 8 |  |  |  |  | 138 |  | 
| 329 | 8 |  |  |  |  | 211 | my %base		= %{ $base->components }; | 
|  | 8 |  |  |  |  | 139 |  | 
| 330 | 8 |  |  |  |  | 228 | my %target; | 
| 331 |  |  |  |  |  |  |  | 
| 332 | 8 | 50 |  |  |  | 22 | if ($components{scheme}) { | 
| 333 | 0 |  |  |  |  | 0 | foreach my $k (qw(scheme user port host path query)) { | 
| 334 | 0 | 0 |  |  |  | 0 | if (exists $components{$k}) { | 
| 335 | 0 |  |  |  |  | 0 | $target{$k} = $components{$k}; | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  | } | 
| 338 |  |  |  |  |  |  | } else { | 
| 339 | 8 | 50 | 33 |  |  | 55 | if ($components{user} or $components{port} or defined($components{host})) { | 
|  |  |  | 33 |  |  |  |  | 
| 340 | 0 |  |  |  |  | 0 | foreach my $k (qw(scheme user port host query)) { | 
| 341 | 0 | 0 |  |  |  | 0 | if (exists $components{$k}) { | 
| 342 | 0 |  |  |  |  | 0 | $target{$k} = $components{$k}; | 
| 343 |  |  |  |  |  |  | } | 
| 344 |  |  |  |  |  |  | } | 
| 345 | 0 |  |  |  |  | 0 | my $path		= $components{path}; | 
| 346 | 0 |  |  |  |  | 0 | $target{path}	= $self->_remove_dot_segments($path); | 
| 347 |  |  |  |  |  |  | } else { | 
| 348 | 8 | 100 |  |  |  | 21 | if ($components{path} eq '') { | 
| 349 | 3 |  |  |  |  | 5 | $target{path}	= $base{path}; | 
| 350 | 3 | 50 |  |  |  | 10 | if ($components{query}) { | 
| 351 | 0 |  |  |  |  | 0 | $target{query}	= $components{query}; | 
| 352 |  |  |  |  |  |  | } else { | 
| 353 | 3 | 50 |  |  |  | 8 | if ($base{query}) { | 
| 354 | 0 |  |  |  |  | 0 | $target{query}	= $base{query}; | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  | } | 
| 357 |  |  |  |  |  |  | } else { | 
| 358 | 5 | 100 |  |  |  | 17 | if ($components{path} =~ m<^/>) { | 
| 359 | 1 |  |  |  |  | 2 | my $path		= $components{path}; | 
| 360 | 1 |  |  |  |  | 4 | $target{path}	= $self->_remove_dot_segments($path); | 
| 361 |  |  |  |  |  |  | } else { | 
| 362 | 4 |  |  |  |  | 15 | my $path		= $self->_merge($base); | 
| 363 | 4 |  |  |  |  | 18 | $target{path}	= $self->_remove_dot_segments($path); | 
| 364 |  |  |  |  |  |  | } | 
| 365 | 5 | 50 |  |  |  | 16 | if (defined($components{query})) { | 
| 366 | 0 |  |  |  |  | 0 | $target{query}	= $components{query}; | 
| 367 |  |  |  |  |  |  | } | 
| 368 |  |  |  |  |  |  | } | 
| 369 | 8 | 50 | 33 |  |  | 48 | if ($base{user} or $base{port} or defined($base{host})) { | 
|  |  |  | 33 |  |  |  |  | 
| 370 | 8 |  |  |  |  | 20 | foreach my $k (qw(user port host)) { | 
| 371 | 24 | 100 |  |  |  | 80 | if (exists $base{$k}) { | 
| 372 | 8 |  |  |  |  | 18 | $target{$k} = $base{$k}; | 
| 373 |  |  |  |  |  |  | } | 
| 374 |  |  |  |  |  |  | } | 
| 375 |  |  |  |  |  |  | } | 
| 376 |  |  |  |  |  |  | } | 
| 377 | 8 | 50 |  |  |  | 23 | if (defined($base{scheme})) { | 
| 378 | 8 |  |  |  |  | 17 | $target{scheme} = $base{scheme}; | 
| 379 |  |  |  |  |  |  | } | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  |  | 
| 382 | 8 | 100 |  |  |  | 22 | if (defined($components{fragment})) { | 
| 383 | 2 |  |  |  |  | 3 | $target{fragment}	= $components{fragment}; | 
| 384 |  |  |  |  |  |  | } | 
| 385 |  |  |  |  |  |  |  | 
| 386 | 8 |  |  |  |  | 171 | return \%target; | 
| 387 |  |  |  |  |  |  | } | 
| 388 | 5 |  |  |  |  | 90 | return $self->components; | 
| 389 |  |  |  |  |  |  | } | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | sub _abs { | 
| 392 | 10 |  |  | 10 |  | 487 | my $self	= shift; | 
| 393 | 10 |  |  |  |  | 179 | my $value	= $self->_string_from_components( $self->resolved_components ); | 
| 394 | 10 |  |  |  |  | 62 | return $value; | 
| 395 |  |  |  |  |  |  | } | 
| 396 |  |  |  |  |  |  |  | 
| 397 |  |  |  |  |  |  | sub as_string { | 
| 398 |  |  |  |  |  |  | my $self	= shift; | 
| 399 |  |  |  |  |  |  | if ($self->has_base) { | 
| 400 |  |  |  |  |  |  | return $self->abs; | 
| 401 |  |  |  |  |  |  | } else { | 
| 402 |  |  |  |  |  |  | return $self->value; | 
| 403 |  |  |  |  |  |  | } | 
| 404 |  |  |  |  |  |  | } | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | sub _string_from_components { | 
| 407 | 10 |  |  | 10 |  | 304 | my $self		= shift; | 
| 408 | 10 |  |  |  |  | 11 | my $components	= shift; | 
| 409 | 10 |  |  |  |  | 18 | my $iri			= ""; | 
| 410 | 10 | 50 |  |  |  | 24 | if (my $s = $components->{scheme}) { | 
| 411 | 10 |  |  |  |  | 24 | $iri	.= "${s}:"; | 
| 412 |  |  |  |  |  |  | } | 
| 413 |  |  |  |  |  |  |  | 
| 414 | 10 | 50 | 33 |  |  | 57 | if ($components->{user} or $components->{port} or defined($components->{host})) { | 
|  |  |  | 33 |  |  |  |  | 
| 415 |  |  |  |  |  |  | # has authority | 
| 416 | 10 |  |  |  |  | 18 | $iri .= "//"; | 
| 417 | 10 | 50 |  |  |  | 37 | if (my $u = $components->{user}) { | 
| 418 | 0 |  |  |  |  | 0 | $iri	.= sprintf('%s@', $u); | 
| 419 |  |  |  |  |  |  | } | 
| 420 | 10 | 50 |  |  |  | 38 | if (defined(my $h = $components->{host})) { | 
| 421 | 10 |  | 50 |  |  | 32 | $iri	.= $h // ''; | 
| 422 |  |  |  |  |  |  | } | 
| 423 | 10 | 50 |  |  |  | 32 | if (my $p = $components->{port}) { | 
| 424 | 0 |  |  |  |  | 0 | $iri	.= ":$p"; | 
| 425 |  |  |  |  |  |  | } | 
| 426 |  |  |  |  |  |  | } | 
| 427 |  |  |  |  |  |  |  | 
| 428 | 10 | 50 |  |  |  | 37 | if (defined(my $p = $components->{path})) { | 
| 429 | 10 |  |  |  |  | 19 | $iri	.= $p; | 
| 430 |  |  |  |  |  |  | } | 
| 431 |  |  |  |  |  |  |  | 
| 432 | 10 | 50 |  |  |  | 21 | if (defined(my $q = $components->{query})) { | 
| 433 | 0 |  |  |  |  | 0 | $iri	.= '?' . $q; | 
| 434 |  |  |  |  |  |  | } | 
| 435 |  |  |  |  |  |  |  | 
| 436 | 10 | 100 |  |  |  | 28 | if (defined(my $f = $components->{fragment})) { | 
| 437 | 2 |  |  |  |  | 5 | $iri	.= '#' . $f; | 
| 438 |  |  |  |  |  |  | } | 
| 439 |  |  |  |  |  |  |  | 
| 440 | 10 |  |  |  |  | 21 | return $iri; | 
| 441 |  |  |  |  |  |  | } | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | sub _encode { | 
| 444 | 0 |  |  | 0 |  |  | my $str	= shift; | 
| 445 | 0 |  |  |  |  |  | $str	=~ s~([%])~'%' . sprintf('%02x', ord($1))~ge;	# gen-delims | 
|  | 0 |  |  |  |  |  |  | 
| 446 | 0 |  |  |  |  |  | $str	=~ s~([/:?#@]|\[|\])~'%' . sprintf('%02x', ord($1))~ge;	# gen-delims | 
|  | 0 |  |  |  |  |  |  | 
| 447 | 0 |  |  |  |  |  | $str	=~ s~([$!&'()*+,;=])~'%' . sprintf('%02x', ord($1))~ge;	# sub-delims | 
|  | 0 |  |  |  |  |  |  | 
| 448 | 0 |  |  |  |  |  | return $str; | 
| 449 |  |  |  |  |  |  | } | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | sub _unencode { | 
| 452 | 0 |  |  | 0 |  |  | my $str	= shift; | 
| 453 | 0 | 0 |  |  |  |  | if (defined($str)) { | 
| 454 | 0 |  |  |  |  |  | $str =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; | 
|  | 0 |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | } | 
| 456 | 0 |  |  |  |  |  | return $str; | 
| 457 |  |  |  |  |  |  | } | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | =item C<< query_form >> | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | Returns a HASH of key-value mappings for the unencoded, parsed query form data. | 
| 462 |  |  |  |  |  |  |  | 
| 463 |  |  |  |  |  |  | =cut | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | sub query_form { | 
| 466 | 0 |  |  | 0 | 1 |  | my $self	= shift; | 
| 467 | 0 |  | 0 |  |  |  | my $q		= $self->query // return; | 
| 468 | 0 |  |  |  |  |  | my @pairs	= split(/&/, $q); | 
| 469 | 0 |  |  |  |  |  | return map { _unencode($_) } map { split(/=/, $_) } @pairs; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | =item C<< set_query_param ( $key => $value ) >> | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | sets the respective query form value and returns a new L object. | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | =cut | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | sub set_query_param { | 
| 479 | 0 |  |  | 0 | 1 |  | my $self	= shift; | 
| 480 | 0 |  | 0 |  |  |  | my $q		= $self->query // return; | 
| 481 | 0 |  |  |  |  |  | my %map		= map { _unencode($_) } map { split(/=/, $_) } split(/&/, $q); | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 482 | 0 |  |  |  |  |  | while (my ($k, $v)	= splice(@_, 0, 2)) { | 
| 483 | 0 |  |  |  |  |  | $map{$k}	= $v; | 
| 484 |  |  |  |  |  |  | } | 
| 485 |  |  |  |  |  |  |  | 
| 486 | 0 |  |  |  |  |  | my %c		= %{ $self->components }; | 
|  | 0 |  |  |  |  |  |  | 
| 487 | 0 |  |  |  |  |  | my @pairs	= map { join('=', (_encode($_), _encode($map{$_}))) } keys %map; | 
|  | 0 |  |  |  |  |  |  | 
| 488 | 0 |  |  |  |  |  | warn Dumper(\@pairs); | 
| 489 | 0 |  |  |  |  |  | $c{query}	= join('&', @pairs); | 
| 490 |  |  |  |  |  |  |  | 
| 491 | 0 |  |  |  |  |  | my $v		= $self->_string_from_components(\%c); | 
| 492 | 0 |  |  |  |  |  | return $self->new( value => $v ); | 
| 493 |  |  |  |  |  |  | } | 
| 494 |  |  |  |  |  |  | } | 
| 495 |  |  |  |  |  |  |  | 
| 496 |  |  |  |  |  |  | 1; | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | __END__ |