File Coverage

blib/lib/Convert/TBX/RNG.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             #
2             # This file is part of Convert-TBX-RNG
3             #
4             # This software is copyright (c) 2013 by Alan K. Melby.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9             package Convert::TBX::RNG;
10 2     2   38140 use strict;
  2         5  
  2         80  
11 2     2   12 use warnings;
  2         4  
  2         67  
12 2     2   2879 use TBX::XCS;
  0            
  0            
13             use feature 'state';
14             use File::Slurp;
15             use Path::Tiny;
16             use autodie;
17             use Carp;
18             use Data::Dumper;
19             use XML::Twig;
20             use File::ShareDir 'dist_dir';
21             use Exporter::Easy (
22             OK => [qw(generate_rng core_structure_rng)],
23             );
24              
25             our $VERSION = '0.04'; # VERSION
26              
27             # ABSTRACT: Create an RNG to validate a TBX dialect
28              
29              
30             #when used as a script: take an XCS file name and print an RNG
31             print ${ generate_rng( xcs_file => $ARGV[0] ) } unless caller;
32              
33              
34             sub generate_rng {
35             my (%args) = @_;
36             if ( not( $args{xcs_file} || $args{xcs_string} || $args{xcs} ) ) {
37             croak "requires either 'xcs_file', 'xcs_string' or 'xcs' parameters";
38             }
39             my $xcs = TBX::XCS->new();
40             if ( $args{xcs_file} ) {
41             $xcs->parse( file => $args{xcs_file} );
42             }
43             elsif($args{xcs_string}) {
44             $xcs->parse( string => $args{xcs_string} );
45             }else{
46             $xcs = $args{xcs};
47             }
48              
49             my $twig = XML::Twig->new(
50             pretty_print => 'indented',
51             output_encoding => 'UTF-8',
52             do_not_chain_handlers =>
53             1, #can be important when things get complicated
54             keep_spaces => 0,
55             no_prolog => 1,
56             );
57              
58             #parse the original RNG
59             $twig->parsefile( _core_structure_rng_location() );
60              
61             #edit the RNG structure to match the XCS constraints
62             _constrain_languages( $twig, $xcs->get_languages() );
63             _constrain_ref_objects( $twig, $xcs->get_ref_objects() );
64             _constrain_meta_cats( $twig, $xcs->get_data_cats() );
65              
66             my $rng = $twig->sprint;
67             return \$rng;
68             }
69              
70             # add the language choices to the langSet specification
71             sub _constrain_languages {
72             my ( $twig, $languages ) = @_;
73              
74             #make an RNG choice for the xml:lang attribute of langSet
75             my $choice = XML::Twig::Elt->new('choice');
76             for my $abbrv ( sort keys %$languages ) {
77             XML::Twig::Elt->new( 'value', $abbrv )->paste($choice);
78             }
79             my $lang_elt = $twig->root->get_xpath(
80             'define[@name="attlist.langSet"]/' .
81             'attribute[@name="xml:lang"]', 0);
82             $choice->paste($lang_elt);
83             return;
84             }
85              
86             # add ref object choices to back matter
87             sub _constrain_ref_objects {
88             # my ( $rng, $ref_objects ) = @_;
89              
90             #unimplemented
91             return;
92             }
93              
94             # constrain meta-data cats by their data cats
95             sub _constrain_meta_cats {
96             my ( $twig, $data_cats ) = @_;
97              
98             # impIDLangTypTgtDtyp includes: admin(Note), descrip(Note), ref, termNote, transac(Note)
99             # must account for ID, xml:lang, type, target, and datatype
100             for my $meta_cat (
101             qw(admin adminNote
102             descripNote ref transac transacNote)
103             )
104             {
105             my $elt = $twig->get_xpath(
106             "//*[\@xml:id='$meta_cat.element']", 0);
107             _edit_meta_cat($elt, $data_cats->{$meta_cat});
108              
109             #we no longer use the attlists
110             $twig->get_xpath( qq, 0)->delete;
111             }
112              
113             _constrain_termCompList($twig, $data_cats->{'termCompList'});
114              
115             # similar to above meta data cats, but with two levels
116             _constrain_termNote($twig, $data_cats->{'termNote'});
117             # no longer use the attlists
118             $twig->get_xpath( 'define[@name="attlist.termNote"]', 0)->delete;
119              
120             # similar to above meta data cats, but with three levels
121             _constrain_descrip($twig, $data_cats->{'descrip'});
122             $twig->get_xpath('define[@name="attlist.descrip"]', 0)->delete;
123              
124             # we leave no reference to this entity
125             $twig->get_xpath( 'define[@name="impIDLangTypTgtDtyp"]', 0)->delete;
126              
127             # hi and xref are similar because all that needs constraining is
128             # an optional type attribute
129             for my $meta_type(qw(hi xref)){
130             _constrain_optional_type($twig, $meta_type, $data_cats->{$meta_type});
131             }
132              
133             return;
134             }
135              
136             # handles elements of impIDLangTypTgtDtyp which do not have level specifications
137             # args: twig element of meta-data cat to be constrained,
138             # array ref containing data cat specs for a meta-data category
139             sub _edit_meta_cat {
140             my ( $meta_cat_elt, $data_cat_list ) = @_;
141             #disallow content if none specified
142             unless ( $data_cat_list && @$data_cat_list ) {
143             $meta_cat_elt->set_outer_xml('');
144             return;
145             }
146              
147             #replace children with rng:choice, with contents based on data categories
148             $meta_cat_elt->cut_children;
149             my $choice = XML::Twig::Elt->new('choice');
150             for my $data_cat ( @{$data_cat_list} ) {
151             _get_rng_group_for_datacat($data_cat)->paste($choice);
152             }
153             $choice->paste($meta_cat_elt);
154              
155             #allow ID, xml:lang, target, and datatype
156             XML::Twig::Elt->new( 'ref', { name => 'impIDLangTgtDtyp' } )
157             ->paste($meta_cat_elt);
158              
159             return;
160             }
161              
162             sub _constrain_termCompList {
163             my ($twig, $data_cat_list) = @_;
164              
165             #disallow all content if none specified
166             if(!$data_cat_list){
167             $twig->get_xpath(
168             '//*[@xml:id="termCompList.element"]', 0)->set_outer_xml('');
169             return;
170             }
171             my $termCompList_type_elt = $twig->get_xpath(
172             '//*[@xml:id="termCompList.type"]', 0);
173              
174             #create choices for type attribute
175             my $choice = XML::Twig::Elt->new('choice');
176             for my $data_cat ( @{$data_cat_list} ) {
177             XML::Twig::Elt->new('value',$data_cat->{'name'})->
178             paste($choice);
179             }
180             $choice->paste($termCompList_type_elt);
181              
182             return;
183             }
184              
185             #use for meta data category with an optional type (hi and xref)
186             sub _constrain_optional_type {
187             my ($twig, $meta_type, $data_cat_list) = @_;
188              
189             my $type_elt = $twig->get_xpath(
190             "//*[\@xml:id='$meta_type.type']", 0);
191              
192             #disallow type if none are specified in XCS
193             if(!$data_cat_list){
194             $type_elt->parent()->delete();
195             return;
196             }
197              
198             #create choices for type attribute
199             my $choice = XML::Twig::Elt->new('choice');
200             for my $data_cat ( @{$data_cat_list} ) {
201             XML::Twig::Elt->new('value',$data_cat->{'name'})->
202             paste($choice);
203             }
204             $choice->paste($type_elt);
205              
206             return;
207             }
208              
209             # args are parsed twig and hash ref of data_categories
210             # constrain allowed values for termNotes at each level
211             sub _constrain_termNote {
212             my ($twig, $data_cat_list) = @_;
213              
214             #elements present at the two levels
215             my $termNote_elt = $twig->get_xpath(
216             '//*[@xml:id="termNote.element"]', 0);
217             my $termNote_termCompGrp_elt = $twig->get_xpath(
218             '//*[@xml:id="termComp.termNote.element"]', 0);
219              
220             #disallow content if none specified
221             unless ( $data_cat_list ) {
222             $termNote_elt->set_outer_xml('');
223             $termNote_termCompGrp_elt->set_outer_xml('');
224             return;
225             }
226              
227             #edit the data categories for the termComp level
228             my @termComp_cats = grep
229             {
230             exists $_->{forTermComp} and
231             $_->{forTermComp} eq 'yes'
232             } @$data_cat_list;
233             _edit_meta_cat($termNote_termCompGrp_elt, \@termComp_cats);
234              
235             #edit the data categories for the other levels
236             _edit_meta_cat($termNote_elt, $data_cat_list);
237              
238             return;
239             }
240              
241             # args are parsed twig and hash ref of data_categories
242             # constrain allowed values for descrips at each level
243             sub _constrain_descrip {
244             my ($twig, $data_cat_list) = @_;
245              
246             # elements present at the three levels
247             my $term_descrip_elt = $twig->get_xpath(
248             '//*[@xml:id="term.descrip.element"]', 0);
249             my $langSet_descrip_elt = $twig->get_xpath(
250             '//*[@xml:id="langSet.descrip.element"]', 0);
251             my $termEntry_descrip_elt = $twig->get_xpath(
252             '//*[@xml:id="termEntry.descrip.element"]', 0);
253              
254             #disallow content if none specified
255             unless ( $data_cat_list ) {
256             $_->set_outer_xml('')
257             for (($term_descrip_elt, $langSet_descrip_elt, $termEntry_descrip_elt));
258             return;
259             }
260              
261             #find the data categories for each level
262             my @term_cats = grep { _descrip_has_level('term',$_) } @$data_cat_list;
263             my @langSet_cats = grep { _descrip_has_level('langSet',$_) } @$data_cat_list;
264             my @termEntry_cats = grep { _descrip_has_level('termEntry',$_) } @$data_cat_list;
265              
266             #edit the allowed types at each level
267             _edit_meta_cat($term_descrip_elt, \@term_cats);
268             _edit_meta_cat($langSet_descrip_elt, \@langSet_cats);
269             _edit_meta_cat($termEntry_descrip_elt, \@termEntry_cats);
270              
271             return;
272             }
273              
274             #check if a descrip data category has a specified level
275             sub _descrip_has_level {
276             my ($level, $data_cat) = @_;
277             return grep {$_ eq $level} @{$data_cat->{levels}};
278             }
279              
280             # arg: hash ref containing data category information
281             # returns an RNG group element containing contents of data category
282             sub _get_rng_group_for_datacat {
283             my ($data_cat) = @_;
284             my $group = XML::Twig::Elt->new('group');
285             if ( $data_cat->{datatype} eq 'picklist' ) {
286             _get_rng_picklist( $data_cat->{choices} )->paste($group);
287             }
288             else {
289             XML::Twig::Elt->new( 'ref',
290             { name => $data_cat->{datatype} } )->paste($group);
291             }
292             _get_rng_attribute( 'type', $data_cat->{name} )->paste($group);
293             return $group;
294             }
295              
296             # arg: attribute name, attribute value
297             # return RNG attribute element with value as contents
298             sub _get_rng_attribute {
299             my ($name, $value) = @_;
300             return XML::Twig::Elt->parse(
301             '' . $value . '' );
302             }
303              
304             #create a element containing values from an array ref
305             sub _get_rng_picklist {
306             my ($picklist) = @_;
307             my $choice = XML::Twig::Elt->new('choice');
308             for my $value (@$picklist) {
309             XML::Twig::Elt->new( 'value', $value )->paste($choice);
310             }
311             return $choice;
312             }
313              
314              
315             sub core_structure_rng {
316             my $rng = read_file( _core_structure_rng_location() );
317             return \$rng;
318             }
319              
320             sub _core_structure_rng_location {
321             return path( dist_dir('Convert-TBX-RNG'), 'TBXcoreStructV02.rng' );
322             }
323              
324             1;
325              
326             __END__