Try to fix duplicate IDs
authorMarius Gavrilescu <marius@ieval.ro>
Fri, 24 Feb 2017 00:27:47 +0000 (00:27 +0000)
committerMarius Gavrilescu <marius@ieval.ro>
Fri, 24 Feb 2017 00:40:20 +0000 (00:40 +0000)
lib/SVG/SpriteMaker.pm
svg-spritemaker
t/SVG-SpriteMaker.t
t/circle.svg
t/rect.svg

index c27a301e68b493d797ccbc14bd804c9d7647d43e..e24b5d9e8931166f89f977e2d2e99cc85cd11f47 100644 (file)
@@ -27,6 +27,7 @@ sub make_sprite {
        my $parser = SVG::Parser->new;
        @images = map {[ $sub->($_) => $parser->parse_file($_) ]} @images;
        my ($x, $mh) = (0, 0);
+       my %ids = map { $_->[0] => 1 } @images; # start with image names
 
        for (@images) {
                my ($img, $doc) = @$_;
@@ -38,6 +39,43 @@ sub make_sprite {
                $svg->attr(version => undef);
                my $view = $sprite->view(id => $img, viewBox => "$x 0 $w $h");
                $x += $w + 5;
+
+               my @all_elems = $svg->getElements;
+               my @duplicate_ids;
+               for my $elem (@all_elems) {
+                       my $id = $elem->attr('id');
+                       next unless $id;
+                       if ($ids{$id}) {
+                               push @duplicate_ids, $id;
+                       } else {
+                               $ids{$id} = 1;
+                       }
+               }
+
+               warn <<"EOF" if @duplicate_ids && !$ENV{SVG_SPRITEMAKER_NO_DUPLICATE_WARNINGS};
+Some IDs (@duplicate_ids) in $img also exist in previous images.
+Trying to fix automatically, but this might produce a broken SVG.
+Fix IDs manually to avoid incorrect output.
+EOF
+
+               for my $oid (@duplicate_ids) {
+                       my $nid = $oid;
+                       $nid .= '_' while $ids{$nid};
+                       $svg->getElementByID($oid)->attr(id => $nid);
+                       for my $elem (@all_elems) {
+                               my %attribs = %{$elem->getAttributes};
+                               for my $key (keys %attribs) {
+                                       if ($attribs{$key} =~ /#$oid\b/) {
+                                               $attribs{$key} =~ s/#$oid\b/#$nid/g;
+                                               $elem->attr($key => $attribs{$key});
+                                       }
+                               }
+                               if ($elem->cdata =~ /#$oid\b/) {
+                                       $elem->cdata($elem->cdata =~ s/#$oid\b/#$nid/gr);
+                               }
+                       }
+               }
+
                $view->getParent->insertAfter($svg, $view);
        }
 
@@ -108,6 +146,16 @@ used:
 
 where I<$prefix> is the value of the first argument.
 
+If an ID is shared between two or more input files, this module will
+try to rename each occurence except for the first one. This operation
+might have false positives (attributes/cdatas that are mistakenly
+identified to contain the ID-to-be-renamed) and false negatives
+(attributes/cdatas that actually contain the ID-to-be-renamed but this
+is missed by the module), and as such SVG::SpriteMaker will warn if
+duplicate IDs are detected. You can suppress this warning by setting
+the C<SVG_SPRITEMAKER_NO_DUPLICATE_WARNINGS> environment variable to a
+true value.
+
 =head1 SEE ALSO
 
 L<svg-spritemaker>, L<https://css-tricks.com/svg-fragment-identifiers-work/>
index 3762063910e647596d4fd4c2379521a21efb86ed..22ed5018a59581d85cbfe7fd2c42620bed1399ee 100755 (executable)
@@ -60,6 +60,16 @@ example in the SYNOPSIS).
 
 =back
 
+If an ID is shared between two or more input files, this program will
+try to rename each occurence except for the first one. This operation
+might have false positives (attributes/cdatas that are mistakenly
+identified to contain the ID-to-be-renamed) and false negatives
+(attributes/cdatas that actually contain the ID-to-be-renamed but this
+is missed by the module), and as such svg-spritemaker will warn if
+duplicate IDs are detected. You can suppress this warning by setting
+the C<SVG_SPRITEMAKER_NO_DUPLICATE_WARNINGS> environment variable to a
+true value.
+
 =head1 SEE ALSO
 
 L<SVG::SpriteMaker>, L<https://css-tricks.com/svg-fragment-identifiers-work/>
index b83a9394ccc72eebf192bebcfda73c15e7e43f4c..4c3719937dd87957335d0a21e7399ee49cdac2ba 100644 (file)
@@ -2,11 +2,15 @@
 use strict;
 use warnings;
 
-use Test::More tests => 3;
+use Test::More tests => 5;
 BEGIN { use_ok('SVG::SpriteMaker') };
 
+# Don't warn, we intentionally have a duplicate ID
+local $ENV{SVG_SPRITEMAKER_NO_DUPLICATE_WARNINGS} = 1;
 my $sprite = make_sprite frag => <t/*.svg>;
 my @elements = $sprite->getFirstChild->getChildren;
 
 ok $sprite->getElementByID('frag-rect'), 'sprite contains #frag-rect';
 ok $sprite->getElementByID('frag-circle'), 'sprite contains #frag-circle';
+like $sprite->xmlify, qr/id="thing"/, 'sprite contains #thing';
+like $sprite->xmlify, qr/id="thing_"/, 'sprite contains #thing_';
index 0e1ecb9b3c4e488ded5edde3c225b44a5c1aefd9..2affb79112647d5f17d438ea694878ee8a34032d 100644 (file)
@@ -1,3 +1,3 @@
 <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 10 10">
-       <circle fill="red" stroke="none" cx="5" cy="5" r="4"/>
+       <circle fill="red" id="thing" stroke="none" cx="5" cy="5" r="4"/>
 </svg>
index 955293db9f73afdbcc9b8af76e866976ff49e674..803f33fe108d9cb641b062beea98875de64e667a 100644 (file)
@@ -1,3 +1,3 @@
 <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 10 10">
-       <rect fill="blue" stroke="none" x="1" y="1" width="8" height="8" rx="1" ry="1"/>
+       <rect fill="blue" id="thing" stroke="none" x="1" y="1" width="8" height="8" rx="1" ry="1"/>
 </svg>
This page took 0.014691 seconds and 4 git commands to generate.