Publié le 03/09/2022
Auteur fobec
Réseaux sociaux
0 partages
0 tweets
0 plus
0 commentaires

Indenter et mettre en couleur un JSON

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>';

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.

Ajouter un commentaire

Les champs marqués d'un * sont obligatoires, les adresses emails se sont pas publiées.

A lire aussi

Réseaux sociaux
Présentation de l'article
Catégorie
php5 - class
Mise a jour
03/09/2022
Visualisation
vu 8 fois
Public
Internaute
Auteur de la publication
Fobec
Admin
Auteur de 267 articles
|BIO_PSEUDO|
Commentaires récents

Publié par Kal747 dans php5

Merci pour l'info !

Publié par Fobec dans logiciel

Bonjour,
lorsque le regitrar masque l'identite du proprietaire, foWhoisClient ne pourra pas communiquer cette information. Par contre, le logiciel cherche la meilleur source d'information p...

Publié par eldiablo dans java

Malheureusement, ce code n'est pas correct.
Cette classe accepte des dates comme : "31/00/2011"

Publié par Patrice dans CMS

Concernant la commande ShellExecute :

Si vous voulez lancer un programme dont le nom est contenu dans une propriété text,
caption, items bref d\'un string ... n'oubliez pas de tran...

Publié par coramarr dans java

Peut-on trouver des infos sur cette suite qui serait utilise en statistiques. Des documents ou ouvrages qui relatent les divers possibilites d'utilisation de cette suite.
Je suis un particulier ni...