Commit 3448f8db authored by Maxime Reyrolle's avatar Maxime Reyrolle

Merge branch...

Merge branch '994-creer-un-connecteur-permettant-d-extraire-les-donnees-a-partir-d-un-xpath-et-a-partir-de-l' into 'master'

Resolve "Créer un connecteur permettant d'extraire les données à partir d'un XPATH et à partir de l'équivalent en JSON"

Closes #994

See merge request libriciel/pole-plate-formes/pastell/pastell!551
parents 3d79ad60 a31979f1
Pipeline #10805 failed with stages
# 3.1.0
## Correction
## Évolutions
- Ajout d'une étape studio de transformation (création de meta-données ou de fichiers supplémentaires)
- Ajout d'un connecteur de transformation générique, permettant d'utiliser du Twig, du xpath et du jsonpath pour créer de nouvelles métadonnées #994
......
# Développement
```bash
git clone https://gitlab.libriciel.fr/libriciel/pole-plate-formes/pastell/pastell.git
docker-compose up -d
```
# Stratégie GIT
- pull (ou clone) et chechout master
......
This diff is collapsed.
<?php
abstract class TransformationConnecteur extends Connecteur
{
abstract public function transform(DonneesFormulaire $donneesFormulaire, array $utilisateur_info): void;
}
<?php
class TransformationTransform extends ConnecteurTypeActionExecutor
{
/**
* @return bool
* @throws NotFoundException
* @throws UnrecoverableException
*/
public function go()
{
$donneesFormulaire = $this->getDonneesFormulaire();
/** @var TransformationConnecteur $transformationConnecteur */
$transformationConnecteur = $this->getConnecteur("transformation");
$utilisateur_info = $this->objectInstancier
->getInstance(DocumentActionEntite::class)
->getCreatorOfDocument(
$this->id_e,
$this->id_d
);
$transformationConnecteur->transform($donneesFormulaire, $utilisateur_info);
$message = "Transformation terminée";
$this->addActionOK($message);
$this->notify($this->action, $this->type, $message);
$this->setLastMessage($message);
return true;
}
}
<?php
require_once __DIR__ . "/lib/TransformationGeneriqueDefinition.class.php";
require_once __DIR__ . "/lib/SimpleTwigRenderer.class.php";
class TransformationGenerique extends TransformationConnecteur
{
/**
* @var DonneesFormulaire
*/
private $connecteurConfig;
private $transformationGeneriqueDefinition;
private $simpleTwigRenderer;
public function __construct(
TransformationGeneriqueDefinition $transformationGeneriqueDefinition,
SimpleTwigRenderer $simpleTwigRenderer
) {
$this->transformationGeneriqueDefinition = $transformationGeneriqueDefinition;
$this->simpleTwigRenderer = $simpleTwigRenderer;
}
public function setConnecteurConfig(DonneesFormulaire $donneesFormulaire)
{
$this->connecteurConfig = $donneesFormulaire;
}
/**
* @param DonneesFormulaire $donneesFormulaire
* @param array $utilisateur_info
*/
public function transform(DonneesFormulaire $donneesFormulaire, array $utilisateur_info): void
{
$result = $this->getNewValue($donneesFormulaire);
foreach ($result as $id => $value) {
$donneesFormulaire->setData($id, $value);
}
}
public function testTransform(DonneesFormulaire $donneesFormulaire): string
{
$result = $this->getNewValue($donneesFormulaire);
return json_encode($result);
}
/**
* @return array
*/
private function getNewValue(DonneesFormulaire $donneesFormulaire): array
{
$transformation_data = $this->transformationGeneriqueDefinition->getData($this->connecteurConfig);
foreach ($transformation_data as $element_id => $expression) {
$transformation_data[$element_id] = $this->simpleTwigRenderer->render(
$expression,
$donneesFormulaire
);
}
return $transformation_data;
}
}
<?php
require_once __DIR__ . "/../lib/TransformationGeneriqueDefinition.class.php";
class TransformationGeneriqueFillData extends ChoiceActionExecutor
{
/**
* @return bool
* @throws Exception
*/
public function go()
{
$definition_array = $this->getRecuperateur()->get('definition');
$id_element_array = $this->getRecuperateur()->get('id_element');
$data = [];
foreach ($id_element_array as $i => $id_element) {
$id_element = trim($id_element);
if (! $id_element) {
continue;
}
$data[$id_element] = $definition_array[$i] ?? "";
}
$transformationGeneriqueDefinition = $this->objectInstancier->getInstance(
TransformationGeneriqueDefinition::class
);
$transformationGeneriqueDefinition->setTransformation(
$this->getConnecteurConfig($this->id_ce),
$data
);
if ($this->getRecuperateur()->get('add_button') === 'add') {
$this->redirect("Connecteur/externalData?id_ce={$this->id_ce}&field={$this->field}");
exit;
}
return true;
}
/**
* @return bool
*/
public function display()
{
$fluxEntiteSQL = $this->objectInstancier->getInstance(FluxEntiteSQL::class);
$all_used = $fluxEntiteSQL->getUsedByConnecteur($this->id_ce, null, $this->id_e);
$flux = "";
if (count($all_used) == 1) {
$flux = $all_used[0]['flux'];
}
$documentType = $this->getDocumentTypeFactory()->getFluxDocumentType($flux);
$this->{'fieldsList'} = ($documentType->getFormulaire()->getFieldsList());
$this->{'flux'} = $flux;
$transformationGeneriqueDefinition = $this->objectInstancier->getInstance(
TransformationGeneriqueDefinition::class
);
$transformation_data = $transformationGeneriqueDefinition->getData(
$this->getConnecteurConfig($this->id_ce)
);
$transformation_data[''] = "";
$this->{'transformation_data'} = $transformation_data;
$this->renderPage(
"Données de transformation",
__DIR__ . "/../template/TransformationGeneriqueFillData.php"
);
return true;
}
/**
* @return bool
*/
public function displayAPI()
{
return false;
}
}
<?php
class TransformationGeneriqueTestExtraction extends ActionExecutor
{
/**
* @return bool
* @throws UnrecoverableException
*/
public function go(): bool
{
/** @var TransformationGenerique $connecteur */
$connecteur = $this->getMyConnecteur();
$donneesFormulaire = $this->getDonneesFormulaireFactory()->getNonPersistingDonneesFormulaire();
$result = $connecteur->testTransform($donneesFormulaire);
$this->setLastMessage("Résultat de l'extraction : " . $result);
return true;
}
}
nom: Transformation générique
type: transformation
formulaire:
page0:
definition:
name: "Définition de l'extraction"
read-only: true
type: file
visionneuse: TransformationGeneriqueVisionneuse
fill-data:
name: "Définition de l'extraction"
edit-only: true
type: externalData
link_name: Décrire les transformations
choice-action: fill-data
action:
test_extraction:
name: Tester la transformation
action-class: TransformationGeneriqueTestExtraction
fill-data:
name: Configurer les transformations
rule:
role_id_e: no-role
action-class: TransformationGeneriqueFillData
\ No newline at end of file
<?php
use Flow\JSONPath\JSONPath;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\SyntaxError;
use Twig\Extension\SandboxExtension;
use Twig\Loader\ArrayLoader;
use Twig\Sandbox\SecurityPolicy;
use Twig\TwigFunction;
class SimpleTwigRenderer
{
public const XPATH_FUNCTION = 'xpath';
public const JSONPATH_FUNCION = 'jsonpath';
private const AUTHORIZED_TWIG_TAGS = ['if','for'];
private const AUHTORIZED_TWIG_FILTERS = ['escape'];
private const AUHTORIZED_TWIG_METHODS = [];
private const AUHTORIZED_TWIG_PROPERTIES = [];
private const AUHTORIZED_TWIG_FUNCTIONS = [];
/**
* @param string $template_as_string
* @param DonneesFormulaire $donneesFormulaire
* @return string
* @throws LoaderError
* @throws SyntaxError
*/
public function render(string $template_as_string, DonneesFormulaire $donneesFormulaire): string
{
$policy = new SecurityPolicy(
self::AUTHORIZED_TWIG_TAGS,
self::AUHTORIZED_TWIG_FILTERS,
self::AUHTORIZED_TWIG_METHODS,
self::AUHTORIZED_TWIG_PROPERTIES,
self::AUHTORIZED_TWIG_FUNCTIONS
);
$sandbox = new SandboxExtension($policy);
$twigEnvironment = new Environment(new ArrayLoader());
$twigEnvironment->addExtension($sandbox);
$function = new TwigFunction(
self::XPATH_FUNCTION,
function ($element_id, $xpath_expression) use ($donneesFormulaire) {
try {
$simpleXMLWrapper = new SimpleXMLWrapper();
$xml = $simpleXMLWrapper->loadFile($donneesFormulaire->getFilePath($element_id));
} catch (SimpleXMLWrapperException $simpleXMLWrapperException) {
$xml = "";
}
if (! $xml) {
return '';
}
$xml_result = $xml->xpath($xpath_expression);
if (empty($xml_result[0])) {
return '';
}
return $xml_result[0];
}
);
$twigEnvironment->addFunction($function);
$function = new TwigFunction(
self::JSONPATH_FUNCION,
function ($element_id, $json_path_expression) use ($donneesFormulaire) {
$file_content = $donneesFormulaire->getFileContent($element_id);
try {
$jsonPath = new JSONPath(json_decode($file_content, true));
} catch (Exception $e) {
return '';
}
$json_result = $jsonPath->find($json_path_expression);
if (empty($json_result[0])) {
return '';
}
return $json_result[0];
}
);
$twigEnvironment->addFunction($function);
return $twigEnvironment
->createTemplate($template_as_string)
->render($donneesFormulaire->getRawDataWithoutPassword());
}
}
<?php
class TransformationGeneriqueDefinition
{
private const ELEMENT_ID = 'definition';
/**
* @param DonneesFormulaire $donneesFormulaire
* @return array
*/
public function getData(DonneesFormulaire $donneesFormulaire): array
{
$file_content = $donneesFormulaire->getFileContent(self::ELEMENT_ID);
if (! $file_content) {
return [];
}
return json_decode($file_content, true);
}
/**
* @param DonneesFormulaire $donneesFormulaire
* @param array $data_definition
* @throws Exception
*/
public function setTransformation(DonneesFormulaire $donneesFormulaire, array $data_definition): void
{
$file_content = json_encode($data_definition);
$donneesFormulaire->addFileFromData(self::ELEMENT_ID, "definition.json", $file_content);
}
}
<?php
/**
* @var $field
* @var array $transformation_data
*
*/
?>
<div id='box_signature' class='box'>
<form action='Connecteur/doExternalData' method='post' id='form_sign'>
<?php $this->displayCSRFInput(); ?>
<input type='hidden' name='id_ce' value='<?php echo $id_ce ?>'/>
<input type='hidden' name='field' value='<?php echo $field ?>'/>
<table class="table table-striped">
<?php
$i = 0;
foreach ($transformation_data as $element_id => $twig_expression) :
$i++;
?>
<tr>
<th class="w500">
<label for="id_element_<?php echo $i; ?>">Identifiant de l'élément</label>
<input name='id_element[]' id='id_element_<?php echo $i; ?>' class="form-control col-md-5" type="text" value="<?php hecho($element_id) ?>" />
</th>
<td>
<label for="defintion_<?php echo $i; ?>">Transformation</label>
<textarea name='definition[]' id='defintion_<?php echo $i; ?>' cols="80" rows="10" class="form-control col-md-5"><?php hecho($twig_expression); ?></textarea>
</td>
</tr>
<?php endforeach ?>
</table>
<a class='btn btn-secondary'
href='Connecteur/editionModif?id_ce=<?php echo $id_ce ?>'>
<i class="fa fa-times-circle"></i>&nbsp;Annuler
</a>
<button type="submit" class="btn btn-primary" id="add_button" name="add_button" value="add">
<i class="fa fa-check"></i>&nbsp;Ajouter un élement
</button>
<button type="submit" class="btn btn-primary" id="submit_button" name="submit_button">
<i class="fa fa-check"></i>&nbsp;Enregistrer
</button>
</form>
</div>
<div class="box">
<a class="collapse-link" data-toggle="collapse" href="#collapse2">
<h2> <i class="fa fa-plus-square"></i>Explications</h2>
</a>
<div class="collapse alert alert-info" id="collapse2">
<p>Identifiant de l'élément représente l'élément qui va recevoir le résultat de la transformation</p>
<p>Transformation représente une expression <a href="https://twig.symfony.com/" target="_blank">twig</a> dont le résultat sera affecté à l'élément associé</p>
<table class="table table-striped" >
<tr>
<th class="w300">Type de transformation</th>
<th>Exemple de transformation</th>
<th>Explication</th>
</tr>
<tr>
<td>Constante</td>
<td>exemple de constante</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>Contenu d'un élement du formulaire</td>
<td> {{ actes_numero }} </td>
<td>&nbsp;</td>
</tr>
<tr>
<td>Mélange constante et élement du formulaire</td>
<td> Actes numéro {{ actes_numero }} concernant {{ agent_prenom }} {{ agent_nom }}</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>Expression conditionnel</td>
<td> {% if actes_nature === 4 %}AR38{% else %}AR48{% endif %}</td>
<td>Si actes_nature est égale à 4, sera remplacé par AR38, sinon AR48</td>
</tr>
<tr>
<td>Expression xpath</td>
<td> {{ xpath('pes_aller','//EnTetePES/CodBud/@V') }}</td>
<td>Extrait l'expression xpath (valeur du code budget) à partir du fichier XML identifié par l'élément pes_aller</td>
</tr>
<tr>
<td>Expression jsonpath</td>
<td> {{ jsonpath('parapheur_metadata','$.metadata1') }}</td>
<td>Extrait l'expression jsonpath (valeur de metadata1) à partir du fichier JSON identifié par l'élément parapheur_metadata</td>
</tr>
</table>
</div>
</div>
<?php if ($flux) : ?>
<div class="box" >
<a class="collapse-link" data-toggle="collapse" href="#collapseExample">
<h2> <i class="fa fa-plus-square"></i>Liste des éléments du flux <b><?php hecho($flux) ?></b> possibles</h2>
</a>
<div class="collapse" id="collapseExample">
<table class="table table-striped ">
<tr>
<th class="w200">Identifiant</th>
<th class="w200">Libellé</th>
<th class="w200">Type</th>
<th>Commentaire</th>
</tr>
<?php /** @var Field $theField */foreach ($fieldsList as $theField) : ?>
<tr>
<td><?php hecho($theField->getName()) ?></td>
<td><?php hecho($theField->getLibelle()) ?></td>
<td><?php hecho($theField->getType()) ?></td>
<td><?php hecho($theField->getProperties('commentaire')) ?></td>
</tr>
<?php endforeach ?>
</table>
</div>
</div>
<?php else : ?>
<div class="alert alert-warning">Associer ce connecteur à un seul flux de l'entité pour avoir la liste des éléments disponibles sur ce flux</div>
<?php endif; ?>
<?php
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
require_once __DIR__ . "/../lib/TransformationGeneriqueDefinition.class.php";
class TransformationGeneriqueVisionneuse extends Visionneuse
{
private $transformationGeneriqueDefinition;
public function __construct(TransformationGeneriqueDefinition $transformationGeneriqueDefinition)
{
$this->transformationGeneriqueDefinition = $transformationGeneriqueDefinition;
}
public function display($filename, $filepath)
{
if (! $filepath) {
echo "Aucune donnée n'a été renseignée";
}
if (! is_readable($filepath)) {
throw new UnrecoverableException("Aucune donnée n'a été renseignée");
}
$content = json_decode(file_get_contents($filepath), true);
?>
<table class="table table-striped" >
<?php foreach ($content as $element_id => $expression) : ?>
<tr>
<th class="w500"><?php hecho($element_id); ?></th>
<td><?php echo nl2br(get_hecho($expression)); ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php
return true;
}
}
\ No newline at end of file
......@@ -31,7 +31,12 @@ class Recuperateur
}
return $this->tableauInput[$name];
}
/**
* @param $name
* @param string $default
* @return string|array
*/
public function get($name, $default = '')
{
if (empty($this->tableauInput[$name])) {
......
<?php
class TransformationGeneriqueTest extends PastellTestCase
{
/**
* @return TransformationGenerique
</