File Coverage

blib/lib/Parse/ACNS.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1 2     2   1485 use 5.008;
  2         7  
  2         77  
2 2     2   10 use strict;
  2         4  
  2         65  
3 2     2   16 use warnings;
  2         5  
  2         123  
4              
5              
6             package Parse::ACNS;
7             our $VERSION = '0.05';
8              
9             =head1 NAME
10              
11             Parse::ACNS - parser for Automated Copyright Notice System (ACNS) XML
12              
13             =head1 SYNOPSIS
14              
15             use Parse::ACNS;
16             my $data = Parse::ACNS->new->parse( XML::LibXML->load_xml( string => $xml ) );
17              
18             =head1 DESCRIPTION
19              
20             ACNS stands for Automated Copyright Notice System. It's an open source,
21             royalty free system that universities, ISP's, or anyone that handles large
22             volumes of copyright notices can implement on their network to increase
23             the efficiency and reduce the costs of responding to the notices...
24              
25             See L for more details.
26              
27             This module parses ACNS XML into a perl data structure. Supports 1.2, 1.1, 1.0,
28             0.7 and 0.6 revisions of the spec. Parser strictly follows XML Schemas, so throws
29             errors on malformed data.
30              
31             However, it B extract ACNS XML from email messages.
32              
33             =head1 SOME ACNS BACKGROUND
34              
35             L released two revisions
36             of the spec (0.6 and 0.7).
37              
38             L took over
39             and named it ACNS 2.0 and released revisions 1.0, 1.1 and several sub-revisions with
40             letters (1.1f, 1.1j, 1.1p).
41              
42             Then it was moved once again to L and revision 1.2
43             was released.
44              
45             =cut
46              
47 2     2   1825 use File::ShareDir ();
  2         15680  
  2         47  
48 2     2   18 use File::Spec ();
  2         4  
  2         36  
49 2     2   11 use Scalar::Util qw(blessed);
  2         4  
  2         206  
50 2     2   970 use XML::Compile::Schema;
  0            
  0            
51              
52             our %CACHE = (
53             );
54              
55             =head1 METHODS
56              
57             =head2 new
58              
59             Constructor, takes list of named arguments.
60              
61             =over 4
62              
63             =item version - version of the specification
64              
65             =over 4
66              
67             =item compat
68              
69             default value, can parse 1.2 to 0.6 XML. Revision 1.2 is backwards
70             compatible with 0.7. Compat schema makes TimeStamp in Infringement/Content/Item
71             optional to make it compatible with 0.6 revision. Everything else new in 1.2 is
72             optional.
73              
74             =item 1.2, 1.1, 1.0, 0.7 or 0.6
75              
76             strict parsing of the specified version.
77              
78             =back
79              
80             =back
81              
82             =cut
83              
84             sub new {
85             my $proto = shift;
86             return (bless { @_ }, ref($proto) || $proto)->init;
87             }
88              
89             sub init {
90             my $self = shift;
91              
92             $self->{'version'} ||= 'compat';
93             unless ( $self->{'version'} =~ /^(compat|0\.[67]|1\.[0-2])$/ ) {
94             require Carp;
95             Carp::croak(
96             "Only compat, 1.2, 1.1, 1.0, 0.7 and 0.6 versions are supported"
97             .", not '". $self->{'version'} ."'"
98             );
99             }
100              
101             return $self;
102             }
103              
104             =head2 parse
105              
106             my $data = Parse::ACNS->new->parse( XML::LibXML->load_xml(...) );
107              
108             Takes L containing an ACNS XML and returns it as a perl
109             struture. Read L on parsing from different sources.
110              
111             Newer versions of the spec describe more messages besides
112             C<< >>, for example C<< >>. Top level element
113             is not returned as part of the result, but you always can get it from XML
114             document:
115              
116             $xml_doc->documentElement->nodeName;
117              
118             To simplify implementation of compat version parsing document can be
119             changed. At this moment XML namespace is adjusted on all elements.
120              
121             Returned data structure follows XML and its Schema, for example:
122              
123             {
124             'Case' => {
125             'ID' => 'A1234567',
126             'Status' => ...,
127             ...
128             },
129             'Complainant' => {
130             'Email' => 'antipiracy@contentowner.com',
131             'Phone' => ...,
132             ...
133             },
134             'Source' => {
135             'TimeStamp' => '2003-08-30T12:34:53Z',
136             'UserName' => 'guest',
137             'Login' => { ... },
138             'IP_Address' => ...,
139             ...
140             }
141             'Service_Provider' => { ... }
142             'Content' => {
143             'Item' => [
144             {
145             'TimeStamp' => '2003-08-30T12:34:53Z',
146             'FileName' => '8Mile.mpg',
147             'Hash' => {
148             'Type' => 'SHA1',
149             '_' => 'EKR94KF985873KD930ER4KD94'
150             },
151             ...
152             },
153             { ... },
154             ...
155             ]
156             },
157             'History' => {
158             'Notice' => [
159             {
160             'ID' => '12321',
161             'TimeStamp' => '2003-08-30T10:23:13Z',
162             '_' => 'freeform text area'
163             },
164             { ... },
165             ...
166             ]
167             },
168             'Notes' => '
169             Open area for freeform text notes, filelists, etc...
170             '
171             }
172              
173             =cut
174              
175             sub parse {
176             my $self = shift;
177             my $xml = shift;
178             my $element = $xml->documentElement->nodeName;
179             if ( $self->{'version'} eq 'any' ) {
180             foreach my $v (qw(1.2 1.1 1.0 0.7 0.6)) {
181             local $@;
182             my $res;
183             return $res if eval { $res = $self->reader($v, $element)->($xml); 1 };
184             }
185             }
186             elsif ( $self->{'version'} eq 'compat' ) {
187             my $root = $xml->documentElement;
188             my $uri = $root->namespaceURI || '';
189             if ( !$uri || ($uri eq 'http://www.movielabs.com/ACNS' && !$root->can('setNamespaceDeclURI')) ) {
190             my $list = $root->getElementsByTagNameNS($root->namespaceURI, '*');
191             $list->unshift($root);
192             $list->foreach(sub {
193             $_->setNamespace('http://www.acns.net/ACNS', $root->prefix, 1);
194             });
195             }
196             elsif ( $uri eq 'http://www.movielabs.com/ACNS' ) {
197             $root->setNamespaceDeclURI($root->prefix, 'http://www.acns.net/ACNS');
198             }
199             elsif ( $uri eq 'http://www.acns.net/ACNS' ) {
200             # do nothing
201             }
202             elsif ( $uri =~ m{^http://www\.acns\.net\b}i ) {
203             $root->setNamespaceDeclURI($root->prefix, 'http://www.acns.net/ACNS');
204             }
205             else {
206             die "Top level element has '$uri' namespace and it's not something we can parse as ACNS";
207             }
208             return $self->reader($self->{'version'}, $element)->($xml);
209             }
210             else {
211             return $self->reader($self->{'version'}, $element)->($xml);
212             }
213             return undef;
214             }
215              
216             my %NS = (
217             '1.0' => 'http://www.movielabs.com/ACNS',
218             '1.1' => 'http://www.movielabs.com/ACNS',
219             '1.2' => 'http://www.acns.net/ACNS',
220             'compat' => 'http://www.acns.net/ACNS',
221             );
222             my %SUPLIMENTARY = (
223             '1.0' => ['xmlmime'],
224             '1.1' => ['xmlmime'],
225             '1.2' => ['xmlmime', 'xmldsig'],
226             );
227              
228             sub reader {
229             my $self = shift;
230             my $version = shift;
231             my $element = shift || 'Infringement';
232              
233             return $CACHE{$version}{'element'}{$element}
234             if $CACHE{$version}{'element'}{$element};
235              
236             my $schema = $CACHE{$version}{'schema'} ||= do {
237             my @paths;
238             push @paths, File::ShareDir::dist_file(
239             'Parse-ACNS',
240             File::Spec->catfile( 'schema', $version, 'acns.xsd' )
241             );
242             if ( $SUPLIMENTARY{$version} ) {
243             push @paths, map File::ShareDir::dist_file(
244             'Parse-ACNS',
245             File::Spec->catfile( 'schema', "$_.xsd" )
246             ), @{$SUPLIMENTARY{$version}};
247             }
248             XML::Compile::Schema->new( \@paths );
249             };
250              
251             use XML::Compile::Util qw/pack_type/;
252             return $CACHE{$version}{'element'}{$element}
253             = $schema->compile( READER => pack_type( $NS{$version}, $element ) );
254              
255             }
256              
257             =head1 AUTHOR
258              
259             Ruslan Zakirov Eruz@bestpractical.comE
260              
261             =head1 LICENSE
262              
263             Under the same terms as perl itself.
264              
265             =cut
266              
267             1;