File Coverage

blib/lib/Web/Microformats2/Document.pm
Criterion Covered Total %
statement 83 85 97.6
branch 13 14 92.8
condition 9 11 81.8
subroutine 16 16 100.0
pod 4 6 66.6
total 125 132 94.7


line stmt bran cond sub pod time code
1             package Web::Microformats2::Document;
2 2     2   14 use Moo;
  2         4  
  2         13  
3 2     2   613 use MooX::HandlesVia;
  2         4  
  2         14  
4 2     2   1338 use Encode qw(encode_utf8);
  2         21671  
  2         169  
5 2     2   15 use JSON qw(decode_json);
  2         4  
  2         20  
6 2     2   327 use List::Util qw(any);
  2         8  
  2         108  
7 2     2   13 use Types::Standard qw(HashRef ArrayRef InstanceOf);
  2         5  
  2         14  
8              
9 2     2   1802 use Web::Microformats2::Item;
  2         4  
  2         1960  
10              
11             has 'top_level_items' => (
12             is => 'lazy',
13             handles_via => 'Array',
14             isa => ArrayRef[InstanceOf['Web::Microformats2::Item']],
15             default => sub { [] },
16             handles => {
17             all_top_level_items => 'elements',
18             add_top_level_item => 'push',
19             count_top_level_items => 'count',
20             has_top_level_items => 'count',
21             },
22             );
23              
24             has 'items' => (
25             is => 'lazy',
26             handles_via => 'Array',
27             isa => ArrayRef[InstanceOf['Web::Microformats2::Item']],
28             default => sub { [] },
29             handles => {
30             add_item => 'push',
31             all_items => 'elements',
32             },
33             );
34              
35             has 'rels' => (
36             is => 'lazy',
37             isa => HashRef,
38             clearer => '_clear_rels',
39             default => sub { {} },
40             );
41              
42             has 'rel_urls' => (
43             is => 'lazy',
44             isa => HashRef,
45             clearer => '_clear_rel_urls',
46             default => sub { {} },
47             );
48              
49             sub as_json {
50 75     75 1 197 my $self = shift;
51              
52 75         1589 my $data_for_json = {
53             rels => $self->rels,
54             'rel-urls' => $self->rel_urls,
55             items => $self->top_level_items,
56             };
57              
58 75         4483 return JSON->new->convert_blessed->utf8->encode( $data_for_json );
59             }
60              
61             sub as_raw_data {
62 1     1 1 585 my $self = shift;
63              
64 1         4 return decode_json( $self->as_json );
65             }
66              
67             sub new_from_json {
68 1     1 1 634 my $class = shift;
69              
70 1         3 my ( $json ) = @_;
71              
72 1         46 my $data_ref = decode_json (encode_utf8($json));
73              
74             my $document = $class->new(
75             rels => $data_ref->{rels} || {},
76             rel_urls => $data_ref->{rel_urls} || {},
77 1   50     21 );
      50        
78              
79 1         2997 for my $deflated_item ( @{ $data_ref->{items} } ) {
  1         4  
80 1         3 my $item = $class->_inflate_item( $deflated_item );
81 1         19 $document->add_top_level_item( $item );
82 1         80 $document->add_item ( $item );
83             }
84              
85 1         57 return $document;
86             }
87              
88             sub _inflate_item {
89 3     3   7 my $class = shift;
90              
91 3         6 my ( $deflated_item ) = @_;
92              
93 3         4 foreach ( @{ $deflated_item->{type} } ) {
  3         10  
94 2         13 s/^h-//;
95             }
96              
97             my $item = Web::Microformats2::Item->new(
98             types => $deflated_item->{type},
99 3         44 );
100              
101 3 100       63 if ( defined $deflated_item->{value} ) {
102 2         36 $item->value( $deflated_item->{value} );
103             }
104              
105 3         59 for my $deflated_child ( @{ $deflated_item->{children} } ) {
  3         10  
106 0         0 $item->add_child ( $class->_inflate_item( $deflated_child ) );
107             }
108              
109 3         5 for my $property ( keys %{ $deflated_item->{properties} } ) {
  3         10  
110 8         15 my $properties_ref = $deflated_item->{properties}->{$property};
111 8         13 for my $property_value ( @{ $properties_ref } ) {
  8         13  
112 8 100       18 if ( ref( $property_value ) ) {
113 2         7 $property_value = $class->_inflate_item( $property_value );
114             }
115 8         20 $item->add_base_property( $property, $property_value );
116             }
117             }
118              
119 3         10 return $item;
120             }
121              
122             sub get_first {
123 1     1 1 745 my $self = shift;
124              
125 1         3 my ( $type ) = @_;
126              
127 1         23 for my $item ( $self->all_items ) {
128 1 50       55 return $item if $item->has_type( $type );
129             }
130              
131 0         0 return;
132             }
133              
134             sub add_rel {
135 48     48 0 85 my $self = shift;
136              
137 48         106 my ( $rel, $url ) = @_;
138              
139 48   100     905 $self->rels->{ $rel } ||= [];
140 48 100   38   781 unless ( any { $_ eq $url } @{ $self->{rels}->{$rel} } ) {
  38         90  
  48         222  
141 41         67 push @{ $self->{rels}->{$rel} }, $url;
  41         231  
142             }
143             }
144              
145             sub add_rel_url {
146 42     42 0 66 my $self = shift;
147              
148 42         80 my ( $url, $rel_url_value_ref ) = @_;
149              
150 42         64 my $current_value;
151 42 100       800 unless ( $current_value = $self->rel_urls->{ $url } ) {
152 38         929 $current_value = $self->rel_urls->{ $url } = {};
153             }
154              
155 42         360 foreach (qw( hreflang media title type text)) {
156 210 100 100     539 if (
157             ( defined $rel_url_value_ref->{ $_ } )
158             && not ( defined $current_value->{ $_ } )
159             ) {
160 43         121 $current_value->{ $_ } = $rel_url_value_ref->{ $_ };
161             }
162             }
163              
164 42   100     198 $current_value->{rels} ||= [];
165 42         79 for my $rel ( @{ $rel_url_value_ref->{rels} }) {
  42         90  
166 48 100   13   165 unless ( any { $_ eq $rel } @{ $current_value->{ rels } } ) {
  13         47  
  48         159  
167 41         63 push @{ $current_value->{ rels } }, $rel;
  41         230  
168             }
169             }
170             }
171              
172              
173             1;
174              
175             =pod
176              
177             =head1 NAME
178              
179             Web::Microformats2::Document - A parsed Microformats2 data structure
180              
181             =head1 DESCRIPTION
182              
183             An object of this class represents a Microformats2 data structure that
184             has been either parsed from an HTML document or deserialized from JSON.
185              
186             The expected use-case is that you will construct document objects either
187             via the L<Web::Microformats2::Parser/parse> method of
188             L<Web::Microformats2::Parser>, or by this class's L</new_from_json>
189             method. Once constructed, we expect you to treat documents as read-only.
190              
191             See Web::Microformats2 for further context and purpose.
192              
193             =head1 METHODS
194              
195             =head2 Class Methods
196              
197             =head3 new_from_json
198              
199             $doc = Web::Microformats2->new_from_json( $json_string )
200              
201             Given a JSON string containing a properly serialized Microformats2 data
202             structure, returns a L<Web::Microformats2::Document> object.
203              
204             =head2 Object Methods
205              
206             =head3 as_json
207              
208             $json = $doc->as_json
209              
210             Returns a JSON representation of this object, created according to
211             Microformats2 serialization rules.
212              
213             =head3 as_raw_data
214              
215             $mf2_data_ref = $doc->as_raw_data
216              
217             Returns a hash reference containing unblessed data structures that map
218             exactly to the JSON version of this object, as defined by Microformats2
219             serialization rules. In other words, it contains C<items>, C<rels>, and
220             C<rel-urls> keys, and builds down from there.
221              
222             Call this if you'd like to parse the Microformats2 metadata out of a
223             document and then work with it at low level, as opposed to (or as well
224             as) using the various convenience methods offered by this class.
225              
226             Equivalent to calling C<decode_json()> (see L<JSON/decode_json>) on the
227             output of C<as_json>.
228              
229             =head3 all_items
230              
231             @items = $doc->all_items;
232              
233             Returns a list of all L<Web::Microformats2::Item> objects this document
234             contains at I<any> level.
235              
236             =head3 all_top_level_items
237              
238             @items = $doc->all_top_level_items;
239              
240             Returns a list of all L<Web::Microformats2::Item> objects this document
241             contains at the top level.
242              
243             =head3 get_first
244              
245             $item = $doc->get_first( $item_type );
246              
247             # So:
248             $entry = $doc->get_first( 'h-entry' );
249             # Or...
250             $entry = $doc->get_first( 'entry' );
251              
252             Given a Microformats2 item-type string -- e.g. "h-entry" (or just
253             "entry") -- returns the first item of that type that this document
254             contains (in document order, depth-first).
255              
256             =head1 AUTHOR
257              
258             Jason McIntosh (jmac@jmac.org)
259              
260             =head1 COPYRIGHT AND LICENSE
261              
262             This software is Copyright (c) 2018 by Jason McIntosh.
263              
264             This is free software, licensed under:
265              
266             The MIT (X11) License