507 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			507 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| # -*- 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
 | |
| # <https://github.com/sympa-community/sympa.git>.
 | |
| #
 | |
| # 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 <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| 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 .= '<ul>';
 | |
|             foreach my $index (0 .. $#{$var}) {
 | |
|                 $html .= '<li> ' . $index . ':';
 | |
|                 $html .= dump_html_var($var->[$index]);
 | |
|                 $html .= '</li>';
 | |
|             }
 | |
|             $html .= '</ul>';
 | |
|         } elsif (ref($var) eq 'HASH'
 | |
|             || ref($var) eq 'Sympa::Scenario'
 | |
|             || ref($var) eq 'Sympa::List') {
 | |
|             $html .= '<ul>';
 | |
|             foreach my $key (sort keys %{$var}) {
 | |
|                 $html .= '<li>' . $key . '=';
 | |
|                 $html .= dump_html_var($var->{$key});
 | |
|                 $html .= '</li>';
 | |
|             }
 | |
|             $html .= '</ul>';
 | |
|         } 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<Function>.
 | |
| 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<Regexp> 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
 | |
|         '<?xml version="1.0" encoding="UTF-8" ?><custom_attributes></custom_attributes>'
 | |
|         if (not defined $custom_attr);
 | |
|     my $XMLstr = '<?xml version="1.0" encoding="UTF-8" ?><custom_attributes>';
 | |
|     foreach my $k (sort keys %{$custom_attr}) {
 | |
|         my $value = $custom_attr->{$k}{value};
 | |
|         $value = '' unless defined $value;
 | |
| 
 | |
|         $XMLstr .=
 | |
|               "<custom_attribute id=\"$k\"><value>"
 | |
|             . Sympa::Tools::Text::encode_html($value, '\000-\037')
 | |
|             . "</value></custom_attribute>";
 | |
|     }
 | |
|     $XMLstr .= "</custom_attributes>";
 | |
|     $XMLstr =~ s/\s*\n\s*/ /g;
 | |
| 
 | |
|     return $XMLstr;
 | |
| }
 | |
| 
 | |
| 1;
 |