Dans le projet en cours, la configuration de l'application repose sur des fichiers JSON que l'utilisateur peut éditer. Pour faciliter la lecture et la modification du JSON, les éditeurs proposent une fonction nommée Syntax Highlighting. Ce terme anglais désigne la mise en couleur du code source précédée bien souvent par le formatage des données.
Présentation du format JSON
Pour rappel, un fichier JSON est un format d'échange de données au même titre que le format XML par exemple. Le but est de transmettre des valeurs d'une application à une autre. Le format JSON s'est imposé comme standard car sa structure est indépendante du langage de programmation et l'encodage en UTF-8 rend possible l'échange de tout type d'information.Dans sa forme native, le format JSON se présente sous forme d'un texte réduit à son minimum. En effet, rien ne sert de s'encombrer d'espaces et d’éléments de style pour échanger des données entre deux serveurs.
Pour cet article, prenons ma liste des courses au format JSON. Dans sa plus simple représentation, les données se présentent sous cette forme:
{"Supermarchu00e9":{"viande":"poulet","u00e9picerie":["pates","riz"],"liquide":["eau
minu00e9rale","vin","jus de
fruit"]},"Primeur":{"fruit":"pommes","lu00e9gume":["carotte","poireaux","haricot"]},"Boulangerie":"pain","Poissonnerie":["crevettes","saumon","homard"],"Maison
de la
presse":{"papeterie":["stylo","cahier","pochette"],"presse":"magazine"}}
Effectivement, cette représentation des valeurs ne facilitent ni la lecture, ni l'édition des données. Pour rendre le format plus lisible pour un humain, il serait judicieux d'ajouter des retours à la ligne, des espaces et un peu de couleurs.
Fonctions de la class JsonHighlighter
L'idée est de permettre à l'utilisateur de l'application d'éditer un fichier JSON. Pour ce faire, les données sont affichées soit dans un TextArea soit dans un conteneur DIV.Indentation du texte
Pour structurer les données, la première étape consiste à indenter le fichier d'origine. Le format JSON suit un certain nombre de règle qui peuvent être mises en évidence. En effet, les données essentielles sont plus faciles à lire avec des retours à la ligne et des décalages au niveau de la marge.Mise en couleur
Un texte uniquement dans la couleur noir est difficile à lire. Cette manière de présenter l'information donne la même importance à chaque mot du texte. Le format JSON repose sur une collection de couples nom/valeur. Dans un premier temps, la couleur met en évidence s'il s'agit d'un clé ou d'une valeur. Dans un seconde temps, le changement de style identifie le type de valeur (string, integer, boolean).
Code source de la classe JsonHighlighter
<?php
namespace VendorString;
/**
* JsonHighlighter
* Indenter et mettre en couleur un Json
* http://www.fobec.com/php5/1187/indenter-mettre-couleur-json.html
* @author Fobec 04/09/2022
*/
class JsonHighlighter {
/**
* Préférences pour l'affichage texte
*/
const RULES_INDENT_TEXT = [
'padding' => ' ',
'line_break' => "\n"
];
/**
* Préférences pour l'affichage HTML
*/
const RULES_INDENT_HTML = [
'padding' => '<span style="width:10px;height:5px;display:inline-block"></span>',
'line_break' => "\n",
'css_key_string' => 'color:#4e8475;font-weight:bold',
'css_subkey_string' => 'color:#693',
'css_value_string' => "color:#c33",
'css_value_bool' => 'color:#2c5c97',
'css_value_num' => 'color:#502d87'
];
/**
* Indenter pour un affichage texte
* @param string $json
* @return string
*/
public function render_text(string $json): string {
$out = $this->indent($json, JsonHighlighter::RULES_INDENT_TEXT);
return $out;
}
/**
* Indenter et appliquer des couleurs pour un affichage HTML
* @param string $json
* @return string
*/
public function render_html(string $json): string {
$out = $this->indent($json, JsonHighlighter::RULES_INDENT_HTML);
$out_hightlight = $this->highlight($out, JsonHighlighter::RULES_INDENT_HTML);
$out = str_replace("\n", '<br>', $out_hightlight);
return $out;
}
/**
* Indenter les lignes
* @param string $json
* @param array $RULES
* @return string|array
*/
private function indent(string $json, array $RULES) {
//Ligne json en cours
$inline = false;
//Niveau d'indentation
$level = 0;
//Caractère à insérer avant
$before_char = '';
//Caractère à insérer après
$after_char = '';
//Sortie
$result = '';
//Contenu vide
if (strlen($json) == 0) {
return $json;
}
for ($i = 0; $i < strlen($json); $i++) {
//Caractère précédent
$prev_char = ($i > 0) ? $json[$i - 1] : '';
//Caractère en cours
$char = $json[$i];
//à insérer avant
$before_char = '';
//à insérer après
$after_char = '';
//Saut à la ligne après {
if (in_array($char, ['{'])) {
$level++;
$after_char = $RULES['line_break'];
} else if (in_array($char, ['{', '[']) && $prev_char == ':') {
$inline = true;
} else if (in_array($char, ['}', ']']) && $inline == true) {
$inline = false;
} else if (in_array($char, ['}'])) {
$before_char .= $RULES['line_break'];
$level--;
}
if (in_array($char, [',']) && $inline == false) {
$after_char = $RULES['line_break'];
}
$result .= $before_char;
//Padding
$last_result_char = strlen($result) > 0 ? $result[strlen($result) - 1] : '';
if ($level > 0 && $last_result_char == $RULES['line_break']) {
for ($loop_level = 0;
$loop_level < $level;
$loop_level++) {
$result .= $RULES['padding'];
}
}
$result .= $char;
$result .= $after_char;
}
return $result;
}
/**
* Mettre en couleur les clés/valeurs du fichier Json
* @param string $json_indent
* @param array $RULES
* @return type
*/
private function highlight(string $json_indent, array $RULES) {
$lines = explode("\n", $json_indent);
$out = [];
foreach ($lines as $line) {
$part_indent = '';
$part_key = '';
$part_value = '';
$is_subkey = false;
//Indentation
$pos_indent = strrpos($line, '</span>');
if (is_numeric($pos_indent)) {
$is_subkey = strpos($line, '</span>') != $pos_indent;
$part_indent = substr($line, 0, $pos_indent + strlen('</span>'));
$line = substr($line, $pos_indent + strlen('</span>'));
}
//couple valeur/clé
$pos_dubble = strpos($line, ':');
if (is_numeric($pos_dubble) && strpos($line, '::') !== $pos_dubble) {
$part_key = substr($line, 0, $pos_dubble);
$part_value = substr($line, $pos_dubble + 1);
$new_key = $this->render_key($part_key, $RULES, $is_subkey);
$new_value = $this->render_value($part_value, $RULES);
$out[] = $part_indent . $new_key . ':' . $new_value;
} else {
$out[] = $part_indent . $line;
}
}
return implode("n", $out);
}
/**
* Apliquer un style aux clés
* @param string $value
* @param array $RULES
* @param bool $is_subkey clé à partir du niveau 2
* @return string
*/
private function render_key(string $value, array $RULES, bool $is_subkey): string {
// if (preg_match_all("/"[w-_:]*"/u", $value, $matches)) {
$list_string_offsets = $this->get_string_offsets($value);
if (count($list_string_offsets) > 0) {
$buf = '';
$buf .= substr($value, 0, $list_string_offsets[0]['start']);
$sub = substr($value, $list_string_offsets[0]['start'], $list_string_offsets[0]['end'] - $list_string_offsets[0]['start'] + 1);
if ($is_subkey == false) {
$buf .= '<span style="' . $RULES['css_key_string'] . '">' . $sub . '</span>';
} else {
$buf .= '<span style="' . $RULES['css_subkey_string'] . '">' . $sub . '</span>';
}
$buf .= substr($value, $list_string_offsets[0]['end'] + 1);
return $buf;
}
return $value;
}
/**
* Appliquer un style en fonction du type de valeur
* @param string $value
* @param array $RULES
* @return string
*/
private function render_value(string $value, array $RULES): string {
$list_string_offsets = $this->get_string_offsets($value);
//String
if (count($list_string_offsets) > 0) {
$icur = 0;
$buf = '';
foreach ($list_string_offsets as $offset) {
$buf .= substr($value, $icur, $offset['start'] - $icur);
$sub = substr($value, $offset['start'], $offset['end'] - $offset['start'] + 1);
$buf .= '<span style="' . $RULES['css_value_string'] . '">' . $sub . '</span>';
$icur = $offset['end'] + 1;
}
$buf .= substr($value, $icur);
return $buf;
}
//Boolean
if (strpos($value, 'true') === 0 || strpos($value, 'false') === 0) {
return '<span style="' . $RULES['css_value_bool'] . '">' . $value . '</span>';
}
//Number
if (preg_match_all("/[d.]*/is", $value, $matches)) {
foreach ($matches[0] as $key) {
$replace = '<span style="' . $RULES['css_value_num'] . '">' . $key . '</span>';
return str_replace($key, $replace, $value);
}
}
return $value;
}
/**
* Déterminer la position des string dans value
* @param string $value
* @return array ['start','end']
*/
private function get_string_offsets(string $value): array {
$cur_offset = 0;
$out = [];
$pos_quote_start = strpos($value, '"', $cur_offset);
while (is_numeric($pos_quote_start)) {
$pos_quote_end = strpos($value, '"', $pos_quote_start + 1);
if (is_numeric($pos_quote_end)) {
$out[] = ['start' => $pos_quote_start, 'end' => $pos_quote_end];
$cur_offset = $pos_quote_end + 1;
}
$pos_quote_start = strpos($value, '"', $cur_offset);
}
return $out;
}
}
Résultat
La JsonHighlighter est prévue pour éditer un fichier de configuration. L'utilisateur édite le fichier JSON dans un visionneur de type DIV. L'indentation et la mise en couleur facile la compréhension des valeurs.
Ci-dessous un exemple d'utilisation
$jsonHighlighter = new JsonHighlighter();
$html=$jsonHighlighter->render_html($sjon);
echo '<div contenteditable="true" spellcheck="false">';
echo $html;
echo '</div>';
$html=$jsonHighlighter->render_html($sjon);
echo '<div contenteditable="true" spellcheck="false">';
echo $html;
echo '</div>';
Dans un premier temps, la classe JsonHighlighter met en forme un fichier JSON issu de la sauvegarde. L'utilisateur modifie les valeurs du fichier de configuration dans une DIV de type contenteditable=true. Puis les données au format JSON sont à nouveau sauvegarder sur le serveur.