init III
This commit is contained in:
29
Perl OTRS/Kernel/GenericInterface/Mapping/OTRSFunctions.xsl
Normal file
29
Perl OTRS/Kernel/GenericInterface/Mapping/OTRSFunctions.xsl
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0"?>
|
||||
<xsl:stylesheet version="1.0"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:func="http://exslt.org/functions"
|
||||
xmlns:otrs="http://otrs.org"
|
||||
extension-element-prefixes="func otrs">
|
||||
|
||||
<func:function name="otrs:date-xsd-to-iso">
|
||||
<xsl:param name="date-time" />
|
||||
<xsl:variable name="formatted">
|
||||
<xsl:value-of select="substring($date-time, 1, 10)" />
|
||||
<xsl:text> </xsl:text>
|
||||
<xsl:value-of select="substring($date-time, 12, 8)" />
|
||||
</xsl:variable>
|
||||
<func:result select="string($formatted)" />
|
||||
</func:function>
|
||||
|
||||
<func:function name="otrs:date-iso-to-xsd">
|
||||
<xsl:param name="date-time" />
|
||||
<xsl:variable name="formatted">
|
||||
<xsl:value-of select="substring($date-time, 1, 10)" />
|
||||
<xsl:text>T</xsl:text>
|
||||
<xsl:value-of select="substring($date-time, 12, 8)" />
|
||||
<xsl:text>Z</xsl:text>
|
||||
</xsl:variable>
|
||||
<func:result select="string($formatted)" />
|
||||
</func:function>
|
||||
|
||||
</xsl:stylesheet>
|
||||
475
Perl OTRS/Kernel/GenericInterface/Mapping/Simple.pm
Normal file
475
Perl OTRS/Kernel/GenericInterface/Mapping/Simple.pm
Normal file
@@ -0,0 +1,475 @@
|
||||
# --
|
||||
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
||||
# --
|
||||
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
# the enclosed file COPYING for license information (GPL). If you
|
||||
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
||||
# --
|
||||
|
||||
package Kernel::GenericInterface::Mapping::Simple;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Kernel::System::VariableCheck qw(IsHashRefWithData IsString IsStringWithData);
|
||||
|
||||
our $ObjectManagerDisabled = 1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::GenericInterface::Mapping::Simple - GenericInterface simple data mapping backend
|
||||
|
||||
=head1 PUBLIC INTERFACE
|
||||
|
||||
=head2 new()
|
||||
|
||||
usually, you want to create an instance of this
|
||||
by using Kernel::GenericInterface::Mapping->new();
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my ( $Type, %Param ) = @_;
|
||||
|
||||
# allocate new hash for object
|
||||
my $Self = {};
|
||||
bless( $Self, $Type );
|
||||
|
||||
# check needed params
|
||||
for my $Needed (qw(DebuggerObject MappingConfig)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
return {
|
||||
Success => 0,
|
||||
ErrorMessage => "Got no $Needed!"
|
||||
};
|
||||
}
|
||||
$Self->{$Needed} = $Param{$Needed};
|
||||
}
|
||||
|
||||
# check mapping config
|
||||
if ( !IsHashRefWithData( $Param{MappingConfig} ) ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got no MappingConfig as hash ref with content!',
|
||||
);
|
||||
}
|
||||
|
||||
# check config - if we have a map config, it has to be a non-empty hash ref
|
||||
if (
|
||||
defined $Param{MappingConfig}->{Config}
|
||||
&& !IsHashRefWithData( $Param{MappingConfig}->{Config} )
|
||||
)
|
||||
{
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got MappingConfig with Data, but Data is no hash ref with content!',
|
||||
);
|
||||
}
|
||||
|
||||
# check configuration
|
||||
my $ConfigCheck = $Self->_ConfigCheck( Config => $Self->{MappingConfig}->{Config} );
|
||||
return $ConfigCheck if !$ConfigCheck->{Success};
|
||||
|
||||
return $Self;
|
||||
}
|
||||
|
||||
=head2 Map()
|
||||
|
||||
provides 1:1 and regex mapping for keys and values
|
||||
also the use of a default for keys and values that were not mapped is possible
|
||||
|
||||
we need the config to be in the following format
|
||||
|
||||
$Self->{MappingConfig}->{Config} = {
|
||||
KeyMapExact => { # optional. key/value pairs for direct replacement
|
||||
'old_value' => 'new_value',
|
||||
'another_old_value' => 'another_new_value',
|
||||
'maps_to_same_value => 'another_new_value',
|
||||
},
|
||||
KeyMapRegEx => { # optional. replace keys with value if current key matches regex
|
||||
'Stat(e|us)' => 'state',
|
||||
'[pP]riority' => 'prio',
|
||||
},
|
||||
KeyMapDefault => { # required. replace keys if the have not been replaced before
|
||||
MapType => 'Keep', # possible values are
|
||||
# 'Keep' (leave unchanged)
|
||||
# 'Ignore' (drop key/value pair)
|
||||
# 'MapTo' (use provided value as default)
|
||||
MapTo => 'new_value', # only used if 'MapType' is 'MapTo'. then required
|
||||
},
|
||||
ValueMap => { # optional.
|
||||
'new_key_name' => { # optional. Replacement for a specific key
|
||||
ValueMapExact => { # optional. key/value pairs for direct replacement
|
||||
'old_value' => 'new_value',
|
||||
'another_old_value' => 'another_new_value',
|
||||
'maps_to_same_value => 'another_new_value',
|
||||
},
|
||||
ValueMapRegEx => { # optional. replace keys with value if current key matches regex
|
||||
'Stat(e|us)' => 'state',
|
||||
'[pP]riority' => 'prio',
|
||||
},
|
||||
},
|
||||
},
|
||||
ValueMapDefault => { # required. replace keys if the have not been replaced before
|
||||
MapType => 'Keep', # possible values are
|
||||
# 'Keep' (leave unchanged)
|
||||
# 'Ignore' (drop key/value pair)
|
||||
# 'MapTo' (use provided value as default)
|
||||
MapTo => 'new_value', # only used if 'MapType' is 'MapTo'. then required
|
||||
},
|
||||
};
|
||||
|
||||
my $ReturnData = $MappingObject->Map(
|
||||
Data => {
|
||||
'original_key' => 'original_value',
|
||||
'another_key' => 'next_value',
|
||||
},
|
||||
);
|
||||
|
||||
my $ReturnData = {
|
||||
'changed_key' => 'changed_value',
|
||||
'original_key' => 'another_changed_value',
|
||||
'another_original_key' => 'default_value',
|
||||
'default_key' => 'changed_value',
|
||||
};
|
||||
|
||||
=cut
|
||||
|
||||
sub Map {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# check data - only accept undef or hash ref
|
||||
if ( defined $Param{Data} && ref $Param{Data} ne 'HASH' ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got Data but it is not a hash ref in Mapping Simple backend!'
|
||||
);
|
||||
}
|
||||
|
||||
# return if data is empty
|
||||
if ( !defined $Param{Data} || !%{ $Param{Data} } ) {
|
||||
return {
|
||||
Success => 1,
|
||||
Data => {},
|
||||
};
|
||||
}
|
||||
|
||||
# prepare short config variable
|
||||
my $Config = $Self->{MappingConfig}->{Config};
|
||||
|
||||
# no config means we just return input data
|
||||
if ( !$Config ) {
|
||||
return {
|
||||
Success => 1,
|
||||
Data => $Param{Data},
|
||||
};
|
||||
}
|
||||
|
||||
# go through keys for replacement
|
||||
my %ReturnData;
|
||||
CONFIGKEY:
|
||||
for my $OldKey ( sort keys %{ $Param{Data} } ) {
|
||||
|
||||
# check if key is valid
|
||||
if ( !IsStringWithData($OldKey) ) {
|
||||
$Self->{DebuggerObject}->Notice( Summary => 'Got an original key that is not valid!' );
|
||||
next CONFIGKEY;
|
||||
}
|
||||
|
||||
# map key
|
||||
my $NewKey;
|
||||
|
||||
# first check in exact (1:1) map
|
||||
if ( $Config->{KeyMapExact} && $Config->{KeyMapExact}->{$OldKey} ) {
|
||||
$NewKey = $Config->{KeyMapExact}->{$OldKey};
|
||||
}
|
||||
|
||||
# if we have no match from exact map, try regex map
|
||||
if ( !$NewKey && $Config->{KeyMapRegEx} ) {
|
||||
KEYMAPREGEX:
|
||||
for my $ConfigKey ( sort keys %{ $Config->{KeyMapRegEx} } ) {
|
||||
next KEYMAPREGEX if $OldKey !~ m{ \A $ConfigKey \z }xms;
|
||||
if ( $ReturnData{ $Config->{KeyMapRegEx}->{$ConfigKey} } ) {
|
||||
$Self->{DebuggerObject}->Notice(
|
||||
Summary =>
|
||||
"The data key '$Config->{KeyMapRegEx}->{$ConfigKey}' already exists!",
|
||||
);
|
||||
next CONFIGKEY;
|
||||
}
|
||||
$NewKey = $Config->{KeyMapRegEx}->{$ConfigKey};
|
||||
last KEYMAPREGEX;
|
||||
}
|
||||
}
|
||||
|
||||
# if we still have no match, apply default
|
||||
if ( !$NewKey ) {
|
||||
|
||||
# check map type options
|
||||
if ( $Config->{KeyMapDefault}->{MapType} eq 'Keep' ) {
|
||||
$NewKey = $OldKey;
|
||||
}
|
||||
elsif ( $Config->{KeyMapDefault}->{MapType} eq 'Ignore' ) {
|
||||
next CONFIGKEY;
|
||||
}
|
||||
elsif ( $Config->{KeyMapDefault}->{MapType} eq 'MapTo' ) {
|
||||
|
||||
# check if we already have a key with the same name
|
||||
if ( $ReturnData{ $Config->{KeyMapDefault}->{MapTo} } ) {
|
||||
$Self->{DebuggerObject}->Notice(
|
||||
Summary => "The data key $Config->{KeyMapDefault}->{MapTo} already exists!",
|
||||
);
|
||||
next CONFIGKEY;
|
||||
}
|
||||
|
||||
$NewKey = $Config->{KeyMapDefault}->{MapTo};
|
||||
}
|
||||
}
|
||||
|
||||
# sanity check - we should have a translated key now
|
||||
if ( !$NewKey ) {
|
||||
return $Self->{DebuggerObject}->Error( Summary => "Could not map data key $NewKey!" );
|
||||
}
|
||||
|
||||
# map value
|
||||
my $OldValue = $Param{Data}->{$OldKey};
|
||||
|
||||
# if value is no string, just pass through
|
||||
if ( !IsString($OldValue) ) {
|
||||
$ReturnData{$NewKey} = $OldValue;
|
||||
next CONFIGKEY;
|
||||
}
|
||||
|
||||
# check if we have a value mapping for the specific key
|
||||
my $ValueMap;
|
||||
if ( $Config->{ValueMap} && $Config->{ValueMap}->{$NewKey} ) {
|
||||
$ValueMap = $Config->{ValueMap}->{$NewKey};
|
||||
}
|
||||
|
||||
if ($ValueMap) {
|
||||
|
||||
# first check in exact (1:1) map
|
||||
if ( $ValueMap->{ValueMapExact} && defined $ValueMap->{ValueMapExact}->{$OldValue} ) {
|
||||
$ReturnData{$NewKey} = $ValueMap->{ValueMapExact}->{$OldValue};
|
||||
next CONFIGKEY;
|
||||
}
|
||||
|
||||
# if we have no match from exact map, try regex map
|
||||
if ( $ValueMap->{ValueMapRegEx} ) {
|
||||
VALUEMAPREGEX:
|
||||
for my $ConfigKey ( sort keys %{ $ValueMap->{ValueMapRegEx} } ) {
|
||||
next VALUEMAPREGEX if $OldValue !~ m{ \A $ConfigKey \z }xms;
|
||||
$ReturnData{$NewKey} = $ValueMap->{ValueMapRegEx}->{$ConfigKey};
|
||||
next CONFIGKEY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# if we had no mapping, apply default
|
||||
|
||||
# keep current value
|
||||
if ( $Config->{ValueMapDefault}->{MapType} eq 'Keep' ) {
|
||||
$ReturnData{$NewKey} = $OldValue;
|
||||
next CONFIGKEY;
|
||||
}
|
||||
|
||||
# map to default value
|
||||
if ( $Config->{ValueMapDefault}->{MapType} eq 'MapTo' ) {
|
||||
$ReturnData{$NewKey} = $Config->{ValueMapDefault}->{MapTo};
|
||||
next CONFIGKEY;
|
||||
}
|
||||
|
||||
# implicit ignore
|
||||
next CONFIGKEY;
|
||||
}
|
||||
|
||||
return {
|
||||
Success => 1,
|
||||
Data => \%ReturnData,
|
||||
};
|
||||
}
|
||||
|
||||
=begin Internal:
|
||||
|
||||
=head2 _ConfigCheck()
|
||||
|
||||
does checks to make sure the config is sane
|
||||
|
||||
my $Return = $MappingObject->_ConfigCheck(
|
||||
Config => { # config as defined for Map
|
||||
...
|
||||
},
|
||||
);
|
||||
|
||||
in case of an error
|
||||
|
||||
$Return => {
|
||||
Success => 0,
|
||||
ErrorMessage => 'An error occurred',
|
||||
};
|
||||
|
||||
in case of a success
|
||||
|
||||
$Return = {
|
||||
Success => 1,
|
||||
};
|
||||
|
||||
=cut
|
||||
|
||||
sub _ConfigCheck {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# just return success if config is undefined or empty hashref
|
||||
my $Config = $Param{Config};
|
||||
if ( !defined $Config ) {
|
||||
return {
|
||||
Success => 1,
|
||||
};
|
||||
}
|
||||
if ( ref $Config ne 'HASH' ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Config is defined but not a hash reference!',
|
||||
);
|
||||
}
|
||||
if ( !IsHashRefWithData($Config) ) {
|
||||
return {
|
||||
Success => 1,
|
||||
};
|
||||
}
|
||||
|
||||
# parse config options for validity
|
||||
my %OnlyStringConfigTypes = (
|
||||
KeyMapExact => 1,
|
||||
KeyMapRegEx => 1,
|
||||
KeyMapDefault => 1,
|
||||
ValueMapDefault => 1,
|
||||
);
|
||||
my %RequiredConfigTypes = (
|
||||
KeyMapDefault => 1,
|
||||
ValueMapDefault => 1,
|
||||
);
|
||||
CONFIGTYPE:
|
||||
for my $ConfigType (qw(KeyMapExact KeyMapRegEx KeyMapDefault ValueMap ValueMapDefault)) {
|
||||
|
||||
# require some types
|
||||
if ( !defined $Config->{$ConfigType} ) {
|
||||
next CONFIGTYPE if !$RequiredConfigTypes{$ConfigType};
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => "Got no $ConfigType, but it is required!",
|
||||
);
|
||||
}
|
||||
|
||||
# check type definition
|
||||
if ( !IsHashRefWithData( $Config->{$ConfigType} ) ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => "Got $ConfigType with Data, but Data is no hash ref with content!",
|
||||
);
|
||||
}
|
||||
|
||||
# check keys and values of these config types
|
||||
next CONFIGTYPE if !$OnlyStringConfigTypes{$ConfigType};
|
||||
for my $ConfigKey ( sort keys %{ $Config->{$ConfigType} } ) {
|
||||
if ( !IsString($ConfigKey) ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => "Got key in $ConfigType which is not a string!",
|
||||
);
|
||||
}
|
||||
if ( !IsString( $Config->{$ConfigType}->{$ConfigKey} ) ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => "Got value for $ConfigKey in $ConfigType which is not a string!",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# check default configuration in KeyMapDefault and ValueMapDefault
|
||||
my %ValidMapTypes = (
|
||||
Keep => 1,
|
||||
Ignore => 1,
|
||||
MapTo => 1,
|
||||
);
|
||||
CONFIGTYPE:
|
||||
for my $ConfigType (qw(KeyMapDefault ValueMapDefault)) {
|
||||
|
||||
# require MapType as a string with a valid value
|
||||
if (
|
||||
!IsStringWithData( $Config->{$ConfigType}->{MapType} )
|
||||
|| !$ValidMapTypes{ $Config->{$ConfigType}->{MapType} }
|
||||
)
|
||||
{
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => "Got no valid MapType in $ConfigType!",
|
||||
);
|
||||
}
|
||||
|
||||
# check MapTo if MapType is set to 'MapTo'
|
||||
if (
|
||||
$Config->{$ConfigType}->{MapType} eq 'MapTo'
|
||||
&& !IsStringWithData( $Config->{$ConfigType}->{MapTo} )
|
||||
)
|
||||
{
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => "Got MapType 'MapTo', but MapTo value is not valid in $ConfigType!",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# check ValueMap
|
||||
if ( IsHashRefWithData( $Config->{ValueMap} ) ) {
|
||||
for my $KeyName ( sort keys %{ $Config->{ValueMap} } ) {
|
||||
|
||||
# require values to be hash ref
|
||||
if ( !IsHashRefWithData( $Config->{ValueMap}->{$KeyName} ) ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => "Got $KeyName in ValueMap, but it is no hash ref with content!",
|
||||
);
|
||||
}
|
||||
|
||||
# possible sub-values are ValueMapExact or ValueMapRegEx and need to be hash ref if defined
|
||||
SUBKEY:
|
||||
for my $SubKeyName (qw(ValueMapExact ValueMapRegEx)) {
|
||||
my $ValueMapType = $Config->{ValueMap}->{$KeyName}->{$SubKeyName};
|
||||
next SUBKEY if !defined $ValueMapType;
|
||||
if ( !IsHashRefWithData($ValueMapType) ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary =>
|
||||
"Got $SubKeyName in $KeyName in ValueMap,"
|
||||
. ' but it is no hash ref with content!',
|
||||
);
|
||||
}
|
||||
|
||||
# key/value pairs of ValueMapExact and ValueMapRegEx must be strings
|
||||
for my $ValueMapTypeKey ( sort keys %{$ValueMapType} ) {
|
||||
if ( !IsString($ValueMapTypeKey) ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary =>
|
||||
"Got key in $SubKeyName in $KeyName in ValueMap which is not a string!",
|
||||
);
|
||||
}
|
||||
if ( !IsString( $ValueMapType->{$ValueMapTypeKey} ) ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary =>
|
||||
"Got value for $ValueMapTypeKey in $SubKeyName in $KeyName in ValueMap"
|
||||
. ' which is not a string!',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# if we arrive here, all checks were OK
|
||||
return {
|
||||
Success => 1,
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=end Internal:
|
||||
|
||||
=head1 TERMS AND CONDITIONS
|
||||
|
||||
This software is part of the OTRS project (L<https://otrs.org/>).
|
||||
|
||||
This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
the enclosed file COPYING for license information (GPL). If you
|
||||
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
|
||||
=cut
|
||||
264
Perl OTRS/Kernel/GenericInterface/Mapping/Test.pm
Normal file
264
Perl OTRS/Kernel/GenericInterface/Mapping/Test.pm
Normal file
@@ -0,0 +1,264 @@
|
||||
# --
|
||||
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
||||
# --
|
||||
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
# the enclosed file COPYING for license information (GPL). If you
|
||||
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
||||
# --
|
||||
|
||||
package Kernel::GenericInterface::Mapping::Test;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Kernel::System::VariableCheck qw(IsHashRefWithData IsStringWithData);
|
||||
|
||||
our $ObjectManagerDisabled = 1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::GenericInterface::Mapping::Test - GenericInterface test data mapping backend
|
||||
|
||||
=head1 PUBLIC INTERFACE
|
||||
|
||||
=head2 new()
|
||||
|
||||
usually, you want to create an instance of this
|
||||
by using Kernel::GenericInterface::Mapping->new();
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my ( $Type, %Param ) = @_;
|
||||
|
||||
# allocate new hash for object
|
||||
my $Self = {};
|
||||
bless( $Self, $Type );
|
||||
|
||||
# check needed params
|
||||
for my $Needed (qw(DebuggerObject MappingConfig)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
|
||||
return {
|
||||
Success => 0,
|
||||
ErrorMessage => "Got no $Needed!"
|
||||
};
|
||||
}
|
||||
$Self->{$Needed} = $Param{$Needed};
|
||||
}
|
||||
|
||||
# check mapping config
|
||||
if ( !IsHashRefWithData( $Param{MappingConfig} ) ) {
|
||||
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got no MappingConfig as hash ref with content!',
|
||||
);
|
||||
}
|
||||
|
||||
# check config - if we have a map config, it has to be a non-empty hash ref
|
||||
if (
|
||||
defined $Param{MappingConfig}->{Config}
|
||||
&& !IsHashRefWithData( $Param{MappingConfig}->{Config} )
|
||||
)
|
||||
{
|
||||
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got MappingConfig with Data, but Data is no hash ref with content!',
|
||||
);
|
||||
}
|
||||
|
||||
return $Self;
|
||||
}
|
||||
|
||||
=head2 Map()
|
||||
|
||||
perform data mapping
|
||||
|
||||
possible config options for value mapping are
|
||||
- 'ToUpper', turns all characters into upper case
|
||||
- 'ToLower', turns all characters into lower case
|
||||
- 'Empty', sets to empty string
|
||||
|
||||
if no config option is provided or one that does not match the options above, the original data will be returned
|
||||
|
||||
my $Result = $MappingObject->Map(
|
||||
Data => { # data payload before mapping
|
||||
...
|
||||
},
|
||||
);
|
||||
|
||||
$Result = {
|
||||
Success => 1, # 0 or 1
|
||||
ErrorMessage => '', # in case of error
|
||||
Data => { # data payload of after mapping
|
||||
...
|
||||
},
|
||||
};
|
||||
|
||||
=cut
|
||||
|
||||
sub Map {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# check data - only accept undef or hash ref
|
||||
if ( defined $Param{Data} && ref $Param{Data} ne 'HASH' ) {
|
||||
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got Data but it is not a hash ref in Mapping Test backend!'
|
||||
);
|
||||
}
|
||||
|
||||
# return if data is empty
|
||||
if ( !defined $Param{Data} || !%{ $Param{Data} } ) {
|
||||
|
||||
return {
|
||||
Success => 1,
|
||||
Data => {},
|
||||
};
|
||||
}
|
||||
|
||||
# no config means that we just return input data
|
||||
if (
|
||||
!defined $Self->{MappingConfig}->{Config}
|
||||
|| !defined $Self->{MappingConfig}->{Config}->{TestOption}
|
||||
)
|
||||
{
|
||||
|
||||
return {
|
||||
Success => 1,
|
||||
Data => $Param{Data},
|
||||
};
|
||||
}
|
||||
|
||||
# check TestOption format
|
||||
if ( !IsStringWithData( $Self->{MappingConfig}->{Config}->{TestOption} ) ) {
|
||||
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got no TestOption as string with value!',
|
||||
);
|
||||
}
|
||||
|
||||
# parse data according to configuration
|
||||
my $ReturnData = {};
|
||||
if ( $Self->{MappingConfig}->{Config}->{TestOption} eq 'ToUpper' ) {
|
||||
$ReturnData = $Self->_ToUpper( Data => $Param{Data} );
|
||||
}
|
||||
elsif ( $Self->{MappingConfig}->{Config}->{TestOption} eq 'ToLower' ) {
|
||||
$ReturnData = $Self->_ToLower( Data => $Param{Data} );
|
||||
}
|
||||
elsif ( $Self->{MappingConfig}->{Config}->{TestOption} eq 'Empty' ) {
|
||||
$ReturnData = $Self->_Empty( Data => $Param{Data} );
|
||||
}
|
||||
else {
|
||||
$ReturnData = $Param{Data};
|
||||
}
|
||||
|
||||
# return result
|
||||
return {
|
||||
Success => 1,
|
||||
Data => $ReturnData,
|
||||
};
|
||||
}
|
||||
|
||||
=begin Internal:
|
||||
|
||||
=head2 _ToUpper()
|
||||
|
||||
change all characters in values to upper case
|
||||
|
||||
my $ReturnData => $MappingObject->_ToUpper(
|
||||
Data => { # data payload before mapping
|
||||
'abc' => 'Def,
|
||||
'ghi' => 'jkl',
|
||||
},
|
||||
);
|
||||
|
||||
$ReturnData = { # data payload after mapping
|
||||
'abc' => 'DEF',
|
||||
'ghi' => 'JKL',
|
||||
};
|
||||
|
||||
=cut
|
||||
|
||||
sub _ToUpper {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $ReturnData = {};
|
||||
for my $Key ( sort keys %{ $Param{Data} } ) {
|
||||
$ReturnData->{$Key} = uc $Param{Data}->{$Key};
|
||||
}
|
||||
|
||||
return $ReturnData;
|
||||
}
|
||||
|
||||
=head2 _ToLower()
|
||||
|
||||
change all characters in values to lower case
|
||||
|
||||
my $ReturnData => $MappingObject->_ToLower(
|
||||
Data => { # data payload before mapping
|
||||
'abc' => 'Def,
|
||||
'ghi' => 'JKL',
|
||||
},
|
||||
);
|
||||
|
||||
$ReturnData = { # data payload after mapping
|
||||
'abc' => 'def',
|
||||
'ghi' => 'jkl',
|
||||
};
|
||||
|
||||
=cut
|
||||
|
||||
sub _ToLower {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $ReturnData = {};
|
||||
for my $Key ( sort keys %{ $Param{Data} } ) {
|
||||
$ReturnData->{$Key} = lc $Param{Data}->{$Key};
|
||||
}
|
||||
|
||||
return $ReturnData;
|
||||
}
|
||||
|
||||
=head2 _Empty()
|
||||
|
||||
set all values to empty string
|
||||
|
||||
my $ReturnData => $MappingObject->_Empty(
|
||||
Data => { # data payload before mapping
|
||||
'abc' => 'Def,
|
||||
'ghi' => 'JKL',
|
||||
},
|
||||
);
|
||||
|
||||
$ReturnData = { # data payload after mapping
|
||||
'abc' => '',
|
||||
'ghi' => '',
|
||||
};
|
||||
|
||||
=cut
|
||||
|
||||
sub _Empty {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $ReturnData = {};
|
||||
for my $Key ( sort keys %{ $Param{Data} } ) {
|
||||
$ReturnData->{$Key} = '';
|
||||
}
|
||||
|
||||
return $ReturnData;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=end Internal:
|
||||
|
||||
=head1 TERMS AND CONDITIONS
|
||||
|
||||
This software is part of the OTRS project (L<https://otrs.org/>).
|
||||
|
||||
This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
the enclosed file COPYING for license information (GPL). If you
|
||||
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
|
||||
=cut
|
||||
379
Perl OTRS/Kernel/GenericInterface/Mapping/XSLT.pm
Normal file
379
Perl OTRS/Kernel/GenericInterface/Mapping/XSLT.pm
Normal file
@@ -0,0 +1,379 @@
|
||||
# --
|
||||
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
||||
# --
|
||||
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
# the enclosed file COPYING for license information (GPL). If you
|
||||
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
||||
# --
|
||||
|
||||
package Kernel::GenericInterface::Mapping::XSLT;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Kernel::System::VariableCheck qw(:all);
|
||||
use Storable;
|
||||
|
||||
our $ObjectManagerDisabled = 1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::GenericInterface::Mapping::XSLT - GenericInterface C<XSLT> data mapping backend
|
||||
|
||||
=head1 PUBLIC INTERFACE
|
||||
|
||||
=head2 new()
|
||||
|
||||
usually, you want to create an instance of this
|
||||
by using Kernel::GenericInterface::Mapping->new();
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my ( $Type, %Param ) = @_;
|
||||
|
||||
# Allocate new hash for object.
|
||||
my $Self = {};
|
||||
bless( $Self, $Type );
|
||||
|
||||
# Check needed params.
|
||||
for my $Needed (qw(DebuggerObject MappingConfig)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
return {
|
||||
Success => 0,
|
||||
ErrorMessage => "Got no $Needed!"
|
||||
};
|
||||
}
|
||||
$Self->{$Needed} = $Param{$Needed};
|
||||
}
|
||||
|
||||
# Check mapping config.
|
||||
if ( !IsHashRefWithData( $Param{MappingConfig} ) ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got no MappingConfig as hash ref with content!',
|
||||
);
|
||||
}
|
||||
|
||||
# Check config - if we have a map config, it has to be a non-empty hash ref.
|
||||
if (
|
||||
defined $Param{MappingConfig}->{Config}
|
||||
&& !IsHashRefWithData( $Param{MappingConfig}->{Config} )
|
||||
)
|
||||
{
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got MappingConfig with Data, but Data is no hash ref with content!',
|
||||
);
|
||||
}
|
||||
|
||||
return $Self;
|
||||
}
|
||||
|
||||
=head2 Map()
|
||||
|
||||
Provides mapping based on C<XSLT> style sheets.
|
||||
Additional data is provided by the function results from various stages in requester and provider.
|
||||
This data can be included in the C<XSLT> mapping as 'DataInclude' structure via configuration.
|
||||
|
||||
my $ReturnData = $MappingObject->Map(
|
||||
Data => {
|
||||
'original_key' => 'original_value',
|
||||
'another_key' => 'next_value',
|
||||
},
|
||||
DataInclude => {
|
||||
RequesterRequestInput => { ... },
|
||||
RequesterRequestPrepareOutput => { ... },
|
||||
RequesterRequestMapOutput => { ... },
|
||||
RequesterResponseInput => { ... },
|
||||
RequesterResponseMapOutput => { ... },
|
||||
RequesterErrorHandlingOutput => { ... },
|
||||
ProviderRequestInput => { ... },
|
||||
ProviderRequestMapOutput => { ... },
|
||||
ProviderResponseInput => { ... },
|
||||
ProviderResponseMapOutput => { ... },
|
||||
ProviderErrorHandlingOutput => { ... },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
my $ReturnData = {
|
||||
'changed_key' => 'changed_value',
|
||||
'original_key' => 'another_changed_value',
|
||||
'another_original_key' => 'default_value',
|
||||
'default_key' => 'changed_value',
|
||||
};
|
||||
|
||||
=cut
|
||||
|
||||
sub Map {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Check data - only accept undef or hash ref or array ref.
|
||||
if ( defined $Param{Data} && ref $Param{Data} ne 'HASH' && ref $Param{Data} ne 'ARRAY' ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got Data but it is not a hash or array ref in Mapping XSLT backend!'
|
||||
);
|
||||
}
|
||||
|
||||
# Check included data - only accept undef or hash ref.
|
||||
if ( defined $Param{DataInclude} && !IsHashRefWithData( $Param{DataInclude} ) ) {
|
||||
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Got DataInclude but it is not a hash ref in Mapping XSLT backend!'
|
||||
);
|
||||
}
|
||||
|
||||
# Return if data is empty.
|
||||
if ( !defined $Param{Data} || !%{ $Param{Data} } ) {
|
||||
return {
|
||||
Success => 1,
|
||||
Data => {},
|
||||
};
|
||||
}
|
||||
|
||||
# Prepare short config variable.
|
||||
my $Config = $Self->{MappingConfig}->{Config};
|
||||
|
||||
# No config means we just return input data.
|
||||
if ( !$Config || !$Config->{Template} ) {
|
||||
return {
|
||||
Success => 1,
|
||||
Data => $Param{Data},
|
||||
};
|
||||
}
|
||||
|
||||
# Load required libraries (XML::LibXML and XML::LibXSLT).
|
||||
LIBREQUIRED:
|
||||
for my $LibRequired (qw(XML::LibXML XML::LibXSLT)) {
|
||||
my $LibFound = $Kernel::OM->Get('Kernel::System::Main')->Require($LibRequired);
|
||||
next LIBREQUIRED if $LibFound;
|
||||
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => "Could not find required library $LibRequired",
|
||||
);
|
||||
}
|
||||
|
||||
# Prepare style sheet.
|
||||
my $LibXSLT = XML::LibXSLT->new();
|
||||
|
||||
# Remove template line breaks and white spaces to plain text lines on the fly, see bug# 14106.
|
||||
my $Template =
|
||||
$Config->{Template}
|
||||
=~ s{ > [ \t\n]+ (?= [^< \t\n] ) }{>}xmsgr
|
||||
=~ s{ (?<! [> \t\n] ) [ \t\n]+ < }{<}xmsgr;
|
||||
|
||||
my ( $StyleDoc, $StyleSheet );
|
||||
eval {
|
||||
$StyleDoc = XML::LibXML->load_xml(
|
||||
string => $Template,
|
||||
no_cdata => 1,
|
||||
);
|
||||
};
|
||||
if ( !$StyleDoc ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Could not load configured XSLT template',
|
||||
Data => $Template,
|
||||
);
|
||||
}
|
||||
eval {
|
||||
$StyleSheet = $LibXSLT->parse_stylesheet($StyleDoc);
|
||||
};
|
||||
if ( !$StyleSheet ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Could not parse configured XSLT template',
|
||||
Data => $@,
|
||||
);
|
||||
}
|
||||
|
||||
# Append the configured include data to the normal data structure.
|
||||
if (
|
||||
IsHashRefWithData( $Param{DataInclude} )
|
||||
&& IsArrayRefWithData( $Config->{DataInclude} )
|
||||
)
|
||||
{
|
||||
my $MergedData = Storable::dclone( $Param{Data} );
|
||||
DATAINCLUDEMODULE:
|
||||
for my $DataIncludeModule ( @{ $Config->{DataInclude} } ) {
|
||||
next DATAINCLUDEMODULE if !$Param{DataInclude}->{$DataIncludeModule};
|
||||
|
||||
# Clone the data include hash to prevent circular data structure references
|
||||
$MergedData->{DataInclude}->{$DataIncludeModule}
|
||||
= Storable::dclone( $Param{DataInclude}->{$DataIncludeModule} );
|
||||
}
|
||||
|
||||
$Self->{DebuggerObject}->Debug(
|
||||
Summary => 'Data merged with DataInclude before mapping',
|
||||
Data => $MergedData,
|
||||
);
|
||||
|
||||
$Param{Data} = $MergedData;
|
||||
}
|
||||
|
||||
# Note: XML::Simple was chosen over alternatives like XML::LibXML and XML::Dumper
|
||||
# due to its simplicity and because we just require a straightforward conversion.
|
||||
# Other modules provide more possibilities but don't allow directly exporting a complete
|
||||
# and clean structure.
|
||||
# Reference:
|
||||
# http://www.perlmonks.org/?node_id=490846
|
||||
# http://stackoverflow.com/questions/12182129/convert-string-to-hash-using-libxml-in-perl
|
||||
|
||||
# XSTL regex recursion.
|
||||
if ( IsArrayRefWithData( $Config->{PreRegExFilter} ) ) {
|
||||
$Self->_RegExRecursion(
|
||||
Data => $Param{Data},
|
||||
Config => $Config->{PreRegExFilter},
|
||||
);
|
||||
$Self->{DebuggerObject}->Debug(
|
||||
Summary => 'Data before mapping after Pre RegExFilter',
|
||||
Data => $Param{Data},
|
||||
);
|
||||
}
|
||||
|
||||
# Convert data to xml structure.
|
||||
$Kernel::OM->Get('Kernel::System::Main')->Require('XML::Simple');
|
||||
my $XMLSimple = XML::Simple->new();
|
||||
my $XMLPre;
|
||||
eval {
|
||||
$XMLPre = $XMLSimple->XMLout(
|
||||
$Param{Data},
|
||||
AttrIndent => 1,
|
||||
ContentKey => '-content',
|
||||
NoAttr => 1,
|
||||
KeyAttr => [],
|
||||
RootName => 'RootElement',
|
||||
);
|
||||
};
|
||||
if ( !$XMLPre ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Could not convert data from Perl to XML before mapping',
|
||||
Data => $@,
|
||||
);
|
||||
}
|
||||
|
||||
# Transform xml data.
|
||||
my ( $XMLSource, $Result );
|
||||
eval {
|
||||
$XMLSource = XML::LibXML->load_xml(
|
||||
string => $XMLPre,
|
||||
no_cdata => 1,
|
||||
);
|
||||
};
|
||||
if ( !$XMLSource ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Could not load data after conversion from Perl to XML',
|
||||
Data => $XMLPre,
|
||||
);
|
||||
}
|
||||
eval {
|
||||
$Result = $StyleSheet->transform($XMLSource);
|
||||
};
|
||||
if ( !$Result ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Could not map data',
|
||||
Data => $@,
|
||||
);
|
||||
}
|
||||
my $XMLPost = $StyleSheet->output_as_bytes($Result);
|
||||
if ( !$XMLPost ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => "Could not write mapped data",
|
||||
);
|
||||
}
|
||||
|
||||
# Convert data back to perl structure.
|
||||
my $ReturnData;
|
||||
eval {
|
||||
$ReturnData = $XMLSimple->XMLin(
|
||||
$XMLPost,
|
||||
ForceArray => 0,
|
||||
ContentKey => '-content',
|
||||
NoAttr => 1,
|
||||
KeyAttr => [],
|
||||
);
|
||||
};
|
||||
if ( !$ReturnData ) {
|
||||
return $Self->{DebuggerObject}->Error(
|
||||
Summary => 'Could not convert data from XML to Perl after mapping',
|
||||
Data => [ $@, $XMLPost ],
|
||||
);
|
||||
}
|
||||
|
||||
# XST regex recursion.
|
||||
if ( IsArrayRefWithData( $Config->{PostRegExFilter} ) ) {
|
||||
$Self->{DebuggerObject}->Debug(
|
||||
Summary => 'Data after mapping before Post RegExFilter',
|
||||
Data => $ReturnData,
|
||||
);
|
||||
$Self->_RegExRecursion(
|
||||
Data => $ReturnData,
|
||||
Config => $Config->{PostRegExFilter},
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
Success => 1,
|
||||
Data => $ReturnData,
|
||||
};
|
||||
}
|
||||
|
||||
sub _RegExRecursion {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Data must be hash ref.
|
||||
return 1 if !IsHashRefWithData( $Param{Data} );
|
||||
|
||||
KEY:
|
||||
for my $Key ( sort keys %{ $Param{Data} } ) {
|
||||
|
||||
# Recurse for array data in value.
|
||||
if ( IsArrayRefWithData( $Param{Data}->{$Key} ) ) {
|
||||
|
||||
ARRAYENTRY:
|
||||
for my $ArrayEntry ( @{ $Param{Data}->{$Key} } ) {
|
||||
$Self->_RegExRecursion(
|
||||
Data => $ArrayEntry,
|
||||
Config => $Param{Config},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# Recurse directly otherwise (assume hash reference).
|
||||
else {
|
||||
$Self->_RegExRecursion(
|
||||
Data => $Param{Data}->{$Key},
|
||||
Config => $Param{Config},
|
||||
);
|
||||
}
|
||||
|
||||
# Apply configured RegEx to key.
|
||||
REGEX:
|
||||
for my $RegEx ( @{ $Param{Config} } ) {
|
||||
next REGEX if $Key !~ m{$RegEx->{Search}};
|
||||
|
||||
# Double-eval the replacement string to allow embedded capturing group replacements,
|
||||
# e.g. Search = '(foo|bar)bar', Replace = '$1foo'
|
||||
# turns 'foobar' into 'foofoo' and 'barbar' into 'barfoo'.
|
||||
my $NewKey = $Key =~ s{$RegEx->{Search}}{ '"' . $RegEx->{Replace} . '"' }eer;
|
||||
|
||||
# Skip if new key is equivalent to old key.
|
||||
next KEY if $NewKey eq $Key;
|
||||
|
||||
# Replace matched key with new one.
|
||||
$Param{Data}->{$NewKey} = delete $Param{Data}->{$Key};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 TERMS AND CONDITIONS
|
||||
|
||||
This software is part of the OTRS project (L<https://otrs.org/>).
|
||||
|
||||
This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
the enclosed file COPYING for license information (GPL). If you
|
||||
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user