package Web::Microformats2::Document;
use Moo;
use MooX::HandlesVia;
use Encode qw(encode_utf8);
use JSON qw(decode_json);
use List::Util qw(any);
use Types::Standard qw(HashRef ArrayRef InstanceOf);
use Web::Microformats2::Item;
has 'top_level_items' => (
is => 'lazy',
handles_via => 'Array',
isa => ArrayRef[InstanceOf['Web::Microformats2::Item']],
default => sub { [] },
handles => {
all_top_level_items => 'elements',
add_top_level_item => 'push',
count_top_level_items => 'count',
has_top_level_items => 'count',
},
);
has 'items' => (
is => 'lazy',
handles_via => 'Array',
isa => ArrayRef[InstanceOf['Web::Microformats2::Item']],
default => sub { [] },
handles => {
add_item => 'push',
all_items => 'elements',
},
);
has 'rels' => (
is => 'lazy',
isa => HashRef,
clearer => '_clear_rels',
default => sub { {} },
);
has 'rel_urls' => (
is => 'lazy',
isa => HashRef,
clearer => '_clear_rel_urls',
default => sub { {} },
);
sub as_json {
my $self = shift;
my $data_for_json = {
rels => $self->rels,
'rel-urls' => $self->rel_urls,
items => $self->top_level_items,
};
return JSON->new->convert_blessed->utf8->encode( $data_for_json );
}
sub as_raw_data {
my $self = shift;
return decode_json( $self->as_json );
}
sub new_from_json {
my $class = shift;
my ( $json ) = @_;
my $data_ref = decode_json (encode_utf8($json));
my $document = $class->new(
rels => $data_ref->{rels} || {},
rel_urls => $data_ref->{rel_urls} || {},
);
for my $deflated_item ( @{ $data_ref->{items} } ) {
my $item = $class->_inflate_item( $deflated_item );
$document->add_top_level_item( $item );
$document->add_item ( $item );
}
return $document;
}
sub _inflate_item {
my $class = shift;
my ( $deflated_item ) = @_;
foreach ( @{ $deflated_item->{type} } ) {
s/^h-//;
}
my $item = Web::Microformats2::Item->new(
types => $deflated_item->{type},
);
if ( defined $deflated_item->{value} ) {
$item->value( $deflated_item->{value} );
}
for my $deflated_child ( @{ $deflated_item->{children} } ) {
$item->add_child ( $class->_inflate_item( $deflated_child ) );
}
for my $property ( keys %{ $deflated_item->{properties} } ) {
my $properties_ref = $deflated_item->{properties}->{$property};
for my $property_value ( @{ $properties_ref } ) {
if ( ref( $property_value ) ) {
$property_value = $class->_inflate_item( $property_value );
}
$item->add_base_property( $property, $property_value );
}
}
return $item;
}
sub get_first {
my $self = shift;
my ( $type ) = @_;
for my $item ( $self->all_items ) {
return $item if $item->has_type( $type );
}
return;
}
sub add_rel {
my $self = shift;
my ( $rel, $url ) = @_;
$self->rels->{ $rel } ||= [];
unless ( any { $_ eq $url } @{ $self->{rels}->{$rel} } ) {
push @{ $self->{rels}->{$rel} }, $url;
}
}
sub add_rel_url {
my $self = shift;
my ( $url, $rel_url_value_ref ) = @_;
my $current_value;
unless ( $current_value = $self->rel_urls->{ $url } ) {
$current_value = $self->rel_urls->{ $url } = {};
}
foreach (qw( hreflang media title type text)) {
if (
( defined $rel_url_value_ref->{ $_ } )
&& not ( defined $current_value->{ $_ } )
) {
$current_value->{ $_ } = $rel_url_value_ref->{ $_ };
}
}
$current_value->{rels} ||= [];
for my $rel ( @{ $rel_url_value_ref->{rels} }) {
unless ( any { $_ eq $rel } @{ $current_value->{ rels } } ) {
push @{ $current_value->{ rels } }, $rel;
}
}
}
1;
=pod
=head1 NAME
Web::Microformats2::Document - A parsed Microformats2 data structure
=head1 DESCRIPTION
An object of this class represents a Microformats2 data structure that
has been either parsed from an HTML document or deserialized from JSON.
The expected use-case is that you will construct document objects either
via the L<Web::Microformats2::Parser/parse> method of
L<Web::Microformats2::Parser>, or by this class's L</new_from_json>
method. Once constructed, we expect you to treat documents as read-only.
See Web::Microformats2 for further context and purpose.
=head1 METHODS
=head2 Class Methods
=head3 new_from_json
$doc = Web::Microformats2->new_from_json( $json_string )
Given a JSON string containing a properly serialized Microformats2 data
structure, returns a L<Web::Microformats2::Document> object.
=head2 Object Methods
=head3 as_json
$json = $doc->as_json
Returns a JSON representation of this object, created according to
Microformats2 serialization rules.
=head3 as_raw_data
$mf2_data_ref = $doc->as_raw_data
Returns a hash reference containing unblessed data structures that map
exactly to the JSON version of this object, as defined by Microformats2
serialization rules. In other words, it contains C<items>, C<rels>, and
C<rel-urls> keys, and builds down from there.
Call this if you'd like to parse the Microformats2 metadata out of a
document and then work with it at low level, as opposed to (or as well
as) using the various convenience methods offered by this class.
Equivalent to calling C<decode_json()> (see L<JSON/decode_json>) on the
output of C<as_json>.
=head3 all_items
@items = $doc->all_items;
Returns a list of all L<Web::Microformats2::Item> objects this document
contains at I<any> level.
=head3 all_top_level_items
@items = $doc->all_top_level_items;
Returns a list of all L<Web::Microformats2::Item> objects this document
contains at the top level.
=head3 get_first
$item = $doc->get_first( $item_type );
# So:
$entry = $doc->get_first( 'h-entry' );
# Or...
$entry = $doc->get_first( 'entry' );
Given a Microformats2 item-type string -- e.g. "h-entry" (or just
"entry") -- returns the first item of that type that this document
contains (in document order, depth-first).
=head1 AUTHOR
Jason McIntosh (jmac@jmac.org)
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2018 by Jason McIntosh.
This is free software, licensed under:
The MIT (X11) License