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.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
<?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;
}
}
$html=$jsonHighlighter->render_html($sjon);
echo '<div contenteditable="true" spellcheck="false">';
echo $html;
echo '</div>';