JSON-Structure
view release on metacpan or search on metacpan
lib/JSON/Structure/JsonSourceLocator.pm view on Meta::CPAN
sub new {
my ( $class, $json_text ) = @_;
my $self = bless {
json_text => $json_text // '',
line_offsets => [],
}, $class;
$self->_build_line_offsets();
return $self;
}
=head2 get_location($path)
Returns a JsonLocation object for the given JSON Pointer path.
my $location = $locator->get_location('#/properties/name');
=cut
sub get_location {
my ( $self, $path ) = @_;
return JSON::Structure::Types::JsonLocation->unknown()
unless defined $path && length( $self->{json_text} );
# Parse the JSON Pointer path into segments
my @segments = $self->_parse_json_pointer($path);
# Find the location in the text
return $self->_find_location_in_text( \@segments );
}
sub _build_line_offsets {
my ($self) = @_;
my @offsets = (0); # First line starts at offset 0
my $text = $self->{json_text};
for ( my $i = 0 ; $i < length($text) ; $i++ ) {
if ( substr( $text, $i, 1 ) eq "\n" ) {
push @offsets, $i + 1;
}
}
$self->{line_offsets} = \@offsets;
}
sub _parse_json_pointer {
my ( $self, $path ) = @_;
# Remove leading # if present (JSON Pointer fragment identifier)
$path =~ s/^#//;
# Handle empty path or just "/"
return () if !defined $path || $path eq '' || $path eq '/';
my @segments;
for my $segment ( split m{/}, $path ) {
next if $segment eq '';
# Unescape JSON Pointer tokens
$segment =~ s/~1/\//g;
$segment =~ s/~0/~/g;
# Handle bracket notation (e.g., "required[0]" -> "required", "0")
if ( $segment =~ /^([^\[]+)\[(.+)\]$/ ) {
push @segments, $1;
my $rest = "[$2]";
while ( $rest =~ /^\[([^\]]+)\](.*)$/ ) {
push @segments, $1;
$rest = $2;
}
}
else {
push @segments, $segment;
}
}
return @segments;
}
sub _offset_to_location {
my ( $self, $offset ) = @_;
return JSON::Structure::Types::JsonLocation->unknown()
if $offset < 0 || $offset > length( $self->{json_text} );
my $offsets = $self->{line_offsets};
# Binary search for the line
my ( $low, $high ) = ( 0, $#$offsets );
while ( $low < $high ) {
my $mid = int( ( $low + $high + 1 ) / 2 );
if ( $offsets->[$mid] <= $offset ) {
$low = $mid;
}
else {
$high = $mid - 1;
}
}
my $line = $low + 1; # 1-based line number
my $column = $offset - $offsets->[$low] + 1; # 1-based column number
return JSON::Structure::Types::JsonLocation->new(
line => $line,
column => $column,
);
}
sub _find_location_in_text {
my ( $self, $segments ) = @_;
my $text = $self->{json_text};
my $pos = 0;
( run in 0.619 second using v1.01-cache-2.11-cpan-71847e10f99 )