# -*- indent-tabs-mode: nil; -*- # vim:ft=perl:et:sw=4 # $Id$ # Sympa - SYsteme de Multi-Postage Automatique # # Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel # Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, # 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites # Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER # Copyright 2018 The Sympa Community. See the AUTHORS.md file at the # top-level directory of this distribution and at # . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . package Sympa::Tools::Data; use strict; use warnings; use Encode qw(); use English qw(-no_match_vars); use POSIX qw(); use XML::LibXML qw(); BEGIN { eval 'use Clone qw()'; } use Sympa::Tools::Text; ## This applies recursively to a data structure ## The transformation subroutine is passed as a ref sub recursive_transformation { my ($var, $subref) = @_; return unless (ref($var)); if (ref($var) eq 'ARRAY') { foreach my $index (0 .. $#{$var}) { if (ref($var->[$index])) { recursive_transformation($var->[$index], $subref); } else { $var->[$index] = &{$subref}($var->[$index]); } } } elsif (ref($var) eq 'HASH') { foreach my $key (keys %{$var}) { if (ref($var->{$key})) { recursive_transformation($var->{$key}, $subref); } else { $var->{$key} = &{$subref}($var->{$key}); } } } return; } ## Dump a variable's content sub dump_var { my ($var, $level, $fd) = @_; return undef unless ($fd); if (ref($var)) { if (ref($var) eq 'ARRAY') { foreach my $index (0 .. $#{$var}) { print $fd "\t" x $level . $index . "\n"; dump_var($var->[$index], $level + 1, $fd); } } elsif (ref($var) eq 'HASH' || ref($var) eq 'Sympa::Scenario' || ref($var) eq 'Sympa::List' || ref($var) eq 'CGI::Fast') { foreach my $key (sort keys %{$var}) { print $fd "\t" x $level . '_' . $key . '_' . "\n"; dump_var($var->{$key}, $level + 1, $fd); } } else { printf $fd "\t" x $level . "'%s'" . "\n", ref($var); } } else { if (defined $var) { print $fd "\t" x $level . "'$var'" . "\n"; } else { print $fd "\t" x $level . "UNDEF\n"; } } } ## Dump a variable's content sub dump_html_var { my ($var) = shift; my $html = ''; if (ref($var)) { if (ref($var) eq 'ARRAY') { $html .= ''; } elsif (ref($var) eq 'HASH' || ref($var) eq 'Sympa::Scenario' || ref($var) eq 'Sympa::List') { $html .= ''; } else { $html .= 'EEEEEEEEEEEEEEEEEEEEE' . ref($var); } } else { if (defined $var) { $html .= Sympa::Tools::Text::encode_html($var); } else { $html .= 'UNDEF'; } } return $html; } # Duplicates a complex variable (faster). # CAUTION: This duplicates blessed elements even if they are # singleton/multiton; this breaks subroutine references. sub clone_var { return Clone::clone($_[0]) if $Clone::VERSION; goto &dup_var; # '&' needed } ## Duplictate a complex variable sub dup_var { my ($var) = @_; if (ref($var)) { if (ref($var) eq 'ARRAY') { my $new_var = []; foreach my $index (0 .. $#{$var}) { $new_var->[$index] = dup_var($var->[$index]); } return $new_var; } elsif (ref($var) eq 'HASH') { my $new_var = {}; foreach my $key (sort keys %{$var}) { $new_var->{$key} = dup_var($var->{$key}); } return $new_var; } } return $var; } #################################################### # get_array_from_splitted_string #################################################### # return an array made on a string splited by ','. # It removes spaces. # # # IN : -$string (+): string to split # # OUT : -ref(ARRAY) # ###################################################### # Note: This is used only by Sympa::List. sub get_array_from_splitted_string { my ($string) = @_; my @array; foreach my $word (split /,/, $string) { $word =~ s/^\s+//; $word =~ s/\s+$//; push @array, $word; } return \@array; } #################################################### # diff_on_arrays #################################################### # Makes set operation on arrays (seen as set, with no double) : # - deleted : A \ B # - added : B \ A # - intersection : A /\ B # - union : A \/ B # # IN : -$setA : ref(ARRAY) - set # -$setB : ref(ARRAY) - set # # OUT : -ref(HASH) with keys : # deleted, added, intersection, union # ####################################################### sub diff_on_arrays { my ($setA, $setB) = @_; my $result = { 'intersection' => [], 'union' => [], 'added' => [], 'deleted' => [] }; my %deleted; my %added; my %intersection; my %union; my %hashA; my %hashB; foreach my $eltA (@$setA) { $hashA{$eltA} = 1; $deleted{$eltA} = 1; $union{$eltA} = 1; } foreach my $eltB (@$setB) { $hashB{$eltB} = 1; $added{$eltB} = 1; if ($hashA{$eltB}) { $intersection{$eltB} = 1; $deleted{$eltB} = 0; } else { $union{$eltB} = 1; } } foreach my $eltA (@$setA) { if ($hashB{$eltA}) { $added{$eltA} = 0; } } foreach my $elt (keys %deleted) { next unless $elt; push @{$result->{'deleted'}}, $elt if ($deleted{$elt}); } foreach my $elt (keys %added) { next unless $elt; push @{$result->{'added'}}, $elt if ($added{$elt}); } foreach my $elt (keys %intersection) { next unless $elt; push @{$result->{'intersection'}}, $elt if ($intersection{$elt}); } foreach my $elt (keys %union) { next unless $elt; push @{$result->{'union'}}, $elt if ($union{$elt}); } return $result; } #################################################### # is_in_array #################################################### # Test if a value is on an array # # IN : -$setA : ref(ARRAY) - set # -$value : a serached value # # OUT : boolean ####################################################### sub is_in_array { my $set = shift; die 'missing parameter "$value"' unless @_; my $value = shift; if (defined $value) { foreach my $elt (@{$set || []}) { next unless defined $elt; return 1 if $elt eq $value; } } else { foreach my $elt (@{$set || []}) { return 1 unless defined $elt; } } return undef; } =over =item smart_eq ( $a, $b ) I. Check if two strings are identical. Parameters: =over =item $a, $b Operands. If both of them are undefined, they are equal. If only one of them is undefined, the are not equal. If C<$b> is a L object and it matches to C<$a>, they are equal. Otherwise, they are compared as strings. =back Returns: If arguments matched, true value. Otherwise false value. =back =cut sub smart_eq { die 'missing argument' if scalar @_ < 2; my ($a, $b) = @_; if (defined $a and defined $b) { if (ref $b eq 'Regexp') { return 1 if $a =~ $b; } else { return 1 if $a eq $b; } } elsif (!defined $a and !defined $b) { return 1; } return undef; } ## convert a string formated as var1="value1";var2="value2"; into a hash. ## Used when extracting from session table some session properties or when ## extracting users preference from user table ## Current encoding is NOT compatible with encoding of values with '"' ## sub string_2_hash { my $data = shift; my %hash; pos($data) = 0; while ($data =~ /\G;?(\w+)\=\"((\\[\"\\]|[^\"])*)\"(?=(;|\z))/g) { my ($var, $val) = ($1, $2); $val =~ s/\\([\"\\])/$1/g; $hash{$var} = $val; } return (%hash); } ## convert a hash into a string formated as var1="value1";var2="value2"; into ## a hash sub hash_2_string { my $refhash = shift; return undef unless ref $refhash eq 'HASH'; my $data_string; foreach my $var (keys %$refhash) { next unless length $var; my $val = $refhash->{$var}; $val = '' unless defined $val; $val =~ s/([\"\\])/\\$1/g; $data_string .= ';' . $var . '="' . $val . '"'; } return ($data_string); } ## compare 2 scalars, string/numeric independant sub smart_lessthan { my ($stra, $strb) = @_; $stra =~ s/^\s+//; $stra =~ s/\s+$//; $strb =~ s/^\s+//; $strb =~ s/\s+$//; $ERRNO = 0; my ($numa, $unparsed) = POSIX::strtod($stra); my $numb; $numb = POSIX::strtod($strb) unless ($ERRNO || $unparsed != 0); if (($stra eq '') || ($strb eq '') || ($unparsed != 0) || $ERRNO) { return $stra lt $strb; } else { return $stra < $strb; } } =over =item sort_uniq ( [ \&comp ], @items ) Returns sorted array of unique elements in the list. Parameters: =over =item \&comp Optional subroutine reference to compare each pairs of elements. It should take two arguments and return negative, zero or positive result. =item @items Items to be sorted. =back This function was added on Sympa 6.2.16. =back =cut sub sort_uniq { my $comp; if (ref $_[0] eq 'CODE') { $comp = shift; } my %items; @items{@_} = (); if ($comp) { return sort { $comp->($a, $b) } keys %items; } else { return sort keys %items; } } # Create a custom attribute from an XML description # IN : A string, XML formed data as stored in database # OUT : HASH data storing custome attributes. # Old name: Sympa::List::parseCustomAttribute(). sub decode_custom_attribute { my $xmldoc = shift; return undef unless defined $xmldoc and length $xmldoc; my $parser = XML::LibXML->new(); my $tree; ## We should use eval to parse to prevent the program to crash if it fails if (ref($xmldoc) eq 'GLOB') { $tree = eval { $parser->parse_fh($xmldoc) }; } else { $tree = eval { $parser->parse_string($xmldoc) }; } return undef unless defined $tree; my $doc = $tree->getDocumentElement; my @custom_attr = $doc->getChildrenByTagName('custom_attribute'); my %ca; foreach my $ca (@custom_attr) { my $id = Encode::encode_utf8($ca->getAttribute('id')); my $value = Encode::encode_utf8($ca->getElementsByTagName('value')); $ca{$id} = {value => $value}; } return \%ca; } # Create an XML Custom attribute to be stored into data base. # IN : HASH data storing custome attributes # OUT : string, XML formed data to be stored in database # Old name: Sympa::List::createXMLCustomAttribute(). sub encode_custom_attribute { my $custom_attr = shift; return '' if (not defined $custom_attr); my $XMLstr = ''; foreach my $k (sort keys %{$custom_attr}) { my $value = $custom_attr->{$k}{value}; $value = '' unless defined $value; $XMLstr .= "" . Sympa::Tools::Text::encode_html($value, '\000-\037') . ""; } $XMLstr .= ""; $XMLstr =~ s/\s*\n\s*/ /g; return $XMLstr; } 1;