This commit is contained in:
2024-10-14 00:08:40 +02:00
parent dbfba56f66
commit 1462d52e13
4572 changed files with 2658864 additions and 0 deletions

View 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>

View 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

View 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

View 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