Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Maarch
maarchRM
Commits
1a2f1f95
Verified
Commit
1a2f1f95
authored
Feb 04, 2022
by
Cyril Vazquez
Browse files
WIP transformation
parent
59060842
Pipeline
#15890
failed with stages
in 36 seconds
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
dependency/Transformation/Processor.php
0 → 100644
View file @
1a2f1f95
<?php
/*
* Copyright (C) 2022 Maarch
*
* This file is part of Maarch RM.
*
* Maarch RM 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 3 of the License, or
* (at your option) any later version.
*
* Maarch RM 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 Maarch RM. If not, see <http://www.gnu.org/licenses/>.
*/
namespace
dependency\Transformation
;
/**
* The transformation engine
*
* @author Cyril VAZQUEZ <cyril.vazquez@maarch.org>
*/
class
Processor
{
/**
* @var JsonPath
*/
protected
$jsonPath
;
/**
* @var mixed
*/
protected
$source
;
/**
* Applies a set of rules on a source object
*
* @param object $source
* @param object $template
*/
public
function
transform
(
$template
,
$source
)
{
$this
->
jsonPath
=
\
laabs
::
newService
(
'dependency/json/Jsonpath'
,
$source
);
return
$this
->
process
(
$template
);
}
protected
function
process
(
$instruction
,
$context
=
null
)
{
if
(
is_string
(
$instruction
))
{
if
(
substr
(
$instruction
,
0
,
2
)
==
'$.'
||
substr
(
$instruction
,
0
,
2
)
==
'@.'
)
{
return
$this
->
processValue
(
$instruction
,
$context
);
}
return
$instruction
;
}
return
$this
->
processTemplate
(
$instruction
,
$context
);
}
protected
function
processTemplate
(
$template
,
$context
=
null
)
{
switch
(
$template
->
type
)
{
case
'object'
:
return
$this
->
processObject
(
$template
,
$context
);
case
'array'
:
return
$this
->
processArray
(
$template
,
$context
);
}
}
protected
function
processObject
(
$template
,
$context
=
null
)
{
$target
=
(
object
)
[];
if
(
isset
(
$template
->
select
))
{
$items
=
$this
->
select
(
$template
->
select
,
$context
);
}
else
{
$items
=
[
$context
];
}
if
(
isset
(
$template
->
properties
))
{
foreach
(
$template
->
properties
as
$name
=>
$property
)
{
if
(
!
$this
->
isExpression
(
$name
))
{
$target
->
{
$name
}
=
$this
->
process
(
$property
,
$context
);
}
else
{
foreach
(
$items
as
$item
)
{
$pname
=
$this
->
process
(
$name
,
$item
);
$pvalue
=
$this
->
process
(
$property
,
$item
);
$target
->
{
$pname
}
=
$pvalue
;
}
}
}
}
return
$target
;
}
protected
function
processTemplateProperties
(
$templates
,
$target
,
$context
=
null
)
{
foreach
(
$templates
as
$template
)
{
}
}
protected
function
processArray
(
$template
,
$context
=
null
)
{
$items
=
$this
->
select
(
$template
->
select
,
$context
);
if
(
!
isset
(
$template
->
items
))
{
return
$items
;
}
$target
=
[];
foreach
(
$items
as
$item
)
{
$target
[]
=
$this
->
process
(
$template
->
items
,
$item
);
}
return
$target
;
}
protected
function
isExpression
(
$value
)
{
return
(
is_string
(
$value
)
&&
(
substr
(
$value
,
0
,
2
)
==
'$.'
||
substr
(
$value
,
0
,
2
)
==
'@.'
)
);
}
protected
function
processValue
(
$path
,
$context
=
null
)
{
$results
=
$this
->
jsonPath
->
query
(
$path
,
$context
);
return
reset
(
$results
);
}
protected
function
select
(
$path
,
$context
=
null
)
{
return
$this
->
jsonPath
->
query
(
$path
,
$context
);
}
}
dependency/Transformation/README.md
0 → 100644
View file @
1a2f1f95
Transformation
==============
Le composant permet de transformer des structures de données
grâce à des modèles paramétrés.
Usage
-----
La classe
`Processor`
fournit le moteur d'application des modèles
aux données d'entrée pour fournir les données de sortie.
Son usage est très simple : une fois le moteur instancié,
la méthode
`transform`
reçoit en paramètres les données en entrée
et le modèle de données;
il retourne les données transformées selon le modèle.
```
$transformationProcessor = new Processor();
$b = $transformationProcessor->transform($a, $template);
```
Modèles
-------
Un modèle est un document qui décrit la structure des données attendues en sortie
et les méthodes d'évaluation, principalement par utilisation des données fournies
en entrée.
Il existe deux types de modèles :
-
`object`
pour les objets
-
`array`
pour les tableaux indexés
Le type est fourni dans la propriété
`type`
du modèle.
Les modèles utilisent deux instructions principales pour accéder aux données d'entrée
et fournir les données de sortie:
-
`select`
retourne un jeu de résultats sous la forme d'un tableau.
Les résultats fournis peuvent ensuite être utilisés tels quels ou soumis à un nouveau
modèle de transformation.
-
`value`
retourne la première valeur trouvée d'après une expression de requête
ou une valeur constante
### Modèle d'objet
Le modèle de type
`object`
décrit une structure qui sera retournée sous la forme d'un objet de
classe standard de base
`stdClass`
.
Il fournit la liste des définitions de propriétés à évaluer avec la structure
`properties`
, qui fait correspondre les noms des propriétés en sortie avec des
modèles de transformation.
```
type: object
properties:
{value} : {template} | {expression}
```
### Modèle de tableau
Le modèle de type
`array`
décrit une structure qui sera retournée sous la forme d'un tableau
indexé.
Il fournit une requête de sélection des éléments à intégrer au tableau via une
instruction
`select`
:
```
type: array
select: {expression}
```
Il peut fournir la définitions des éléments à évaluer avec la structure
`items`
.
Chaque élément de données sélectionné par l'instruction
`select`
se verra
appliquer le modèle.
```
type: array
select: {expression}
items: {template} | {expression}
```
Valeurs
-------
L'instruction
`value`
fournit une requête de sélection de la valeur de sortie
via une expression :
```
value: {expression}
```
Les expressions sont le plus souvent utilisées pour fournir des valeurs
de propriétés ou éléments de tableaux scalaires.
Exemples
--------
### Objet simple
Le modèle suivant va prodire un objet comportant 2 propriétés issues des données
sources de type objet, mais en modifiant les noms :
```
type: object
properties:
targetProperty1: $.sourceProperty1
targetProperty2: $.sourceProperty2
```
### Objet avec propriétés nommées dynamiquement
Le modèle suivant va prodire un objet comportant 2 propriétés dont
**
le nom et
les valeurs
**
sont fournies par des expressions:
```
type: object
properties:
$.path.to.sourcePropertyName1: $.path.to.sourcePropertyValue1
$.path.to.sourcePropertyName2: $.path.to.sourcePropertyValue2
```
### Objet avec propriétés dynamiques
Le modèle suivant va prodire un objet comportant autant de propriétés que de résultats
fournis par l'expression
`select`
, dont le nom et les valeurs sont fournies par des expressions:
```
type: object
select: $.path.to.array.of.items
properties:
@.relative.path.to.sourcePropertyNames: @.relative.path.to.sourcePropertyValues
```
### Tableau de valeurs de propriété d'objet
```
type: array
select: $.sourceArray[*]
items: @.sourcePropertyName
```
dependency/json/JsonPath.php
0 → 100644
View file @
1a2f1f95
<?php
/*
* Copyright (C) 2021 Maarch
*
* This file is part of dependency json.
*
* Dependency json is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dependency json 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with dependency json. If not, see <http://www.gnu.org/licenses/>.
*/
namespace
dependency\json
;
/**
* A jsonPath implementation for PHP
* Based on https://github.com/Peekmo/JsonPath
*/
class
JsonPath
{
private
$data
=
null
;
private
$result
=
[];
private
$keywords
=
[
'='
,
')'
,
'!'
,
'<'
,
'>'
];
public
function
__construct
(
$data
)
{
$this
->
data
=
$data
;
}
public
function
query
(
$expr
,
$context
=
null
)
{
if
(
is_null
(
$context
)
||
substr
(
$expr
,
0
,
2
)
==
'$.'
)
{
$context
=
$this
->
data
;
}
else
{
$expr
=
substr_replace
(
$expr
,
'$.'
,
0
,
2
);
}
$x
=
$this
->
normalize
(
$expr
);
if
(
$x
)
{
$expr
=
preg_replace
(
"/^
\\
$;/"
,
""
,
$x
);
$this
->
trace
(
$expr
,
$context
,
"$"
);
return
$this
->
result
;
}
}
// normalize path expression
private
function
normalize
(
$expression
)
{
// Replaces filters by #0 #1...
$expression
=
preg_replace_callback
(
array
(
"/[\['](\??\(.*?\))[\]']/"
,
"/\['(.*?)'\]/"
),
array
(
&
$this
,
"tempFilters"
),
$expression
);
// ; separator between each elements
$expression
=
preg_replace
(
array
(
"/'?\.'?|\['?/"
,
"/;;;|;;/"
,
"/;$|'?\]|'$/"
),
array
(
";"
,
";..;"
,
""
),
$expression
);
// Restore filters
$expression
=
preg_replace_callback
(
"/#([0-9]+)/"
,
array
(
&
$this
,
"restoreFilters"
),
$expression
);
$this
->
result
=
array
();
// result array was temporarily used as a buffer ..
return
$expression
;
}
/**
* Pushs the filter into the list
* @param string $filter
* @return string
*/
private
function
tempFilters
(
$filter
)
{
$f
=
$filter
[
1
];
$elements
=
explode
(
'\''
,
$f
);
// Hack to make "dot" works on filters
for
(
$i
=
0
,
$m
=
0
;
$i
<
count
(
$elements
);
$i
++
)
{
if
(
$m
%
2
==
0
)
{
if
(
$i
>
0
&&
substr
(
$elements
[
$i
-
1
],
0
,
1
)
==
'\\'
)
{
continue
;
}
$e
=
explode
(
'.'
,
$elements
[
$i
]);
$str
=
''
;
$first
=
true
;
foreach
(
$e
as
$substr
)
{
if
(
$first
)
{
$str
=
$substr
;
$first
=
false
;
continue
;
}
$end
=
null
;
if
(
false
!==
$pos
=
$this
->
strpos_array
(
$substr
,
$this
->
keywords
))
{
list
(
$substr
,
$end
)
=
array
(
substr
(
$substr
,
0
,
$pos
),
substr
(
$substr
,
$pos
,
strlen
(
$substr
)));
}
$str
.
=
'['
.
$substr
.
']'
;
if
(
null
!==
$end
)
{
$str
.
=
$end
;
}
}
$elements
[
$i
]
=
$str
;
}
$m
++
;
}
return
"[#"
.
(
array_push
(
$this
->
result
,
implode
(
'\''
,
$elements
))
-
1
)
.
"]"
;
}
/**
* Get a filter back
* @param string $filter
* @return mixed
*/
private
function
restoreFilters
(
$filter
)
{
return
$this
->
result
[
$filter
[
1
]];
}
/**
* Builds json path expression
* @param string $path
* @return string
*/
private
function
asPath
(
$path
)
{
$expr
=
explode
(
";"
,
$path
);
$fullPath
=
"$"
;
for
(
$i
=
1
,
$n
=
count
(
$expr
);
$i
<
$n
;
$i
++
)
{
$fullPath
.
=
preg_match
(
"/^[0-9*]+$/"
,
$expr
[
$i
])
?
(
"["
.
$expr
[
$i
]
.
"]"
)
:
(
"['"
.
$expr
[
$i
]
.
"']"
);
}
return
$fullPath
;
}
private
function
store
(
$p
,
$v
)
{
if
(
$p
)
{
$this
->
result
[]
=
$v
;
}
return
!!
$p
;
}
private
function
trace
(
$expr
,
$val
,
$path
)
{
if
(
$expr
!==
""
)
{
$x
=
explode
(
";"
,
$expr
);
$loc
=
array_shift
(
$x
);
$x
=
implode
(
";"
,
$x
);
if
(
is_array
(
$val
)
&&
array_key_exists
(
$loc
,
$val
))
{
$this
->
trace
(
$x
,
$val
[
$loc
],
$path
.
";"
.
$loc
);
}
else
if
(
is_object
(
$val
)
&&
property_exists
(
$val
,
$loc
))
{
$this
->
trace
(
$x
,
$val
->
{
$loc
},
$path
.
";"
.
$loc
);
}
else
if
(
$loc
==
"*"
)
{
$this
->
walk
(
$loc
,
$x
,
$val
,
$path
,
array
(
&
$this
,
"_callback_03"
));
}
else
if
(
$loc
===
".."
)
{
$this
->
trace
(
$x
,
$val
,
$path
);
$this
->
walk
(
$loc
,
$x
,
$val
,
$path
,
array
(
&
$this
,
"_callback_04"
));
}
else
if
(
preg_match
(
"/^\(.*?\)$/"
,
$loc
))
{
// [(expr)]
$this
->
trace
(
$this
->
evalx
(
$loc
,
$val
,
substr
(
$path
,
strrpos
(
$path
,
";"
)
+
1
))
.
";"
.
$x
,
$val
,
$path
);
}
else
if
(
preg_match
(
"/^\?\(.*?\)$/"
,
$loc
))
{
// [?(expr)]
$this
->
walk
(
$loc
,
$x
,
$val
,
$path
,
array
(
&
$this
,
"_callback_05"
));
}
else
if
(
preg_match
(
"/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/"
,
$loc
))
{
// [start:end:step] phyton slice syntax
$this
->
slice
(
$loc
,
$x
,
$val
,
$path
);
}
else
if
(
preg_match
(
"/,/"
,
$loc
))
{
// [name1,name2,...]
for
(
$s
=
preg_split
(
"/'?,'?/"
,
$loc
),
$i
=
0
,
$n
=
count
(
$s
);
$i
<
$n
;
$i
++
)
$this
->
trace
(
$s
[
$i
]
.
";"
.
$x
,
$val
,
$path
);
}
}
else
{
$this
->
store
(
$path
,
$val
);
}
}
private
function
_callback_03
(
$m
,
$l
,
$x
,
$v
,
$p
)
{
$this
->
trace
(
$m
.
";"
.
$x
,
$v
,
$p
);
}
private
function
_callback_04
(
$m
,
$l
,
$x
,
$v
,
$p
)
{
if
(
is_array
(
$v
))
{
if
(
is_array
(
$v
[
$m
])
||
is_object
(
$v
[
$m
]))
{
$this
->
trace
(
"..;"
.
$x
,
$v
[
$m
],
$p
.
";"
.
$m
);
}
}
elseif
(
is_object
(
$v
))
{
if
(
is_array
(
$v
->
{
$m
})
||
is_object
(
$v
->
{
$m
}))
{
$this
->
trace
(
"..;"
.
$x
,
$v
->
{
$m
},
$p
.
";"
.
$m
);
}
}
}
private
function
_callback_05
(
$m
,
$l
,
$x
,
$v
,
$p
)
{
if
(
$this
->
evalx
(
preg_replace
(
"/^\?\((.*?)\)$/"
,
"$1"
,
$l
),
$v
[
$m
]))
{
$this
->
trace
(
$m
.
";"
.
$x
,
$v
,
$p
);
}
}
private
function
walk
(
$loc
,
$expr
,
$val
,
$path
,
$f
)
{
if
(
$val
)
{
foreach
(
$val
as
$m
=>
$v
)
{
call_user_func
(
$f
,
$m
,
$loc
,
$expr
,
$val
,
$path
);
}
}
}
private
function
slice
(
$loc
,
$expr
,
$v
,
$path
)
{
$s
=
explode
(
":"
,
preg_replace
(
"/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/"
,
"$1:$2:$3"
,
$loc
));
$len
=
count
(
$v
);
$start
=
(
int
)
$s
[
0
]
?
$s
[
0
]
:
0
;
$end
=
(
int
)
$s
[
1
]
?
$s
[
1
]
:
$len
;
$step
=
(
int
)
$s
[
2
]
?
$s
[
2
]
:
1
;
$start
=
(
$start
<
0
)
?
max
(
0
,
$start
+
$len
)
:
min
(
$len
,
$start
);
$end
=
(
$end
<
0
)
?
max
(
0
,
$end
+
$len
)
:
min
(
$len
,
$end
);
for
(
$i
=
$start
;
$i
<
$end
;
$i
+=
$step
)
{
$this
->
trace
(
$i
.
";"
.
$expr
,
$v
,
$path
);
}
}
/**
* @param string $x filter
* @param array $v node
*
* @param string $vname
* @return string
*/
private
function
evalx
(
$x
,
$v
,
$vname
=
null
)
{
$name
=
""
;
$expr
=
preg_replace
(
array
(
"/
\\
$/"
,
"/@/"
),
array
(
"
\$
this->data"
,
"
\$
v"
),
$x
);
if
(
is_object
(
$v
))
{
$expr
=
preg_replace
(
"#\[([a-zA-Z_
\x7f
-
\xff
][a-zA-Z0-9_
\x7f
-
\xff
]*)\]#"
,
"->$1"
,
$expr
);
}
else
{
$expr
=
preg_replace
(
"#\[([a-zA-Z_
\x7f
-
\xff
][a-zA-Z0-9_
\x7f
-
\xff
]*)\]#"
,
"['$1']"
,
$expr
);
}
$res
=
eval
(
"
\$
name =
$expr
;"
);
if
(
$res
===
false
)
{
print
(
"(jsonPath) SyntaxError: "
.
$expr
);
}
else
{
return
$name
;
}
}
private
function
toObject
(
$array
)
{
//$o = (object)'';
$o
=
new
\
stdClass
();
foreach
(
$array
as
$key
=>
$value
)
{
if
(
is_array
(
$value
))
{
$value
=
$this
->
toObject
(
$value
);
}
$o
->
$key
=
$value
;
}
return
$o
;
}
/**
* Search one of the given needs in the array
* @param string $haystack
* @param array $needles
* @return bool|string
*/
private
function
strpos_array
(
$haystack
,
array
$needles
)
{
$closer
=
10000
;
foreach
(
$needles
as
$needle
)
{
if
(
false
!==
$pos
=
strpos
(
$haystack
,
$needle
))
{
if
(
$pos
<
$closer
)
{
$closer
=
$pos
;
}
}
}
return
10000
===
$closer
?
false
:
$closer
;
}
}
src/bundle/medona/Controller/ArchiveTransfer.php
View file @
1a2f1f95
...
...
@@ -1124,34 +1124,11 @@ class ArchiveTransfer extends abstractMessage
return
$mapping
->
inputs
->
{
$namespace
};
}
protected
function
applyTransformationRules
(
$
source
,
$rules
,
$
t
ar
get
=
null
)
protected
function
applyTransformationRules
(
$
archiveDescription
,
$rules
,
$ar
chive
)
{
$
jsonPath
=
\
laabs
::
newService
(
'dependency/
json/Jsonpath'
,
$source
);
$
transformationProcessor
=
\
laabs
::
newService
(
'dependency/
Transformation/Processor'
);