aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/json/json_formatter.rs
blob: 47680b1e94311818aac2f0e77158632741c1bf8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//! ref: composer/src/Composer/Json/JsonFormatter.php

use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg};
use shirabe_php_shim::{PhpMixed, function_exists, mb_convert_encoding, pack};

pub struct JsonFormatter;

impl JsonFormatter {
    /**
     * This code is based on the function found at:
     *  http://recursive-design.com/blog/2008/03/11/format-json-with-php/
     *
     * Originally licensed under MIT by Dave Perrett <mail@recursive-design.com>
     */
    pub fn format(json: String, unescape_unicode: bool, unescape_slashes: bool) -> String {
        let mut result = String::new();
        let mut pos: usize = 0;
        let indent_str = "    ";
        let new_line = "\n";
        let mut out_of_quotes = true;
        let mut buffer = String::new();
        let mut noescape = true;

        let chars: Vec<char> = json.chars().collect();
        let str_len = chars.len();

        for i in 0..str_len {
            let char_ = chars[i];

            if char_ == '"' && noescape {
                out_of_quotes = !out_of_quotes;
            }

            if !out_of_quotes {
                buffer.push(char_);
                noescape = if char_ == '\\' { !noescape } else { true };
                continue;
            }
            if !buffer.is_empty() {
                if unescape_slashes {
                    buffer = buffer.replace("\\/", "/");
                }

                if unescape_unicode && function_exists("mb_convert_encoding") {
                    buffer = Preg::replace_callback(
                        r"/(\\+)u([0-9a-f]{4})/i",
                        |matches: &indexmap::IndexMap<CaptureKey, String>| -> String {
                            let m0 = matches
                                .get(&CaptureKey::ByIndex(0))
                                .cloned()
                                .unwrap_or_default();
                            let m1 = matches
                                .get(&CaptureKey::ByIndex(1))
                                .cloned()
                                .unwrap_or_default();
                            let m2 = matches
                                .get(&CaptureKey::ByIndex(2))
                                .cloned()
                                .unwrap_or_default();
                            let l = m1.len();

                            if l % 2 != 0 {
                                let code = i64::from_str_radix(&m2, 16).unwrap_or(0);
                                if code >= 0xD800 && code <= 0xDFFF {
                                    return m0;
                                }

                                return "\\".repeat(l - 1)
                                    + &mb_convert_encoding(
                                        pack("H*", &[PhpMixed::String(m2)]),
                                        "UTF-8",
                                        "UCS-2BE",
                                    );
                            }

                            m0
                        },
                        &buffer,
                    )
                    .unwrap_or(buffer);
                }

                result.push_str(&buffer);
                result.push(char_);
                buffer = String::new();
                continue;
            }

            let mut char_str = char_.to_string();

            if char_ == ':' {
                char_str.push(' ');
            } else if char_ == '}' || char_ == ']' {
                pos -= 1;
                let prev_char = if i > 0 { chars[i - 1] } else { '\0' };

                if prev_char != '{' && prev_char != '[' {
                    result.push_str(new_line);
                    result.push_str(&indent_str.repeat(pos));
                } else {
                    result = result.trim_end().to_string();
                }
            }

            result.push_str(&char_str);

            if char_ == ',' || char_ == '{' || char_ == '[' {
                result.push_str(new_line);

                if char_ == '{' || char_ == '[' {
                    pos += 1;
                }

                result.push_str(&indent_str.repeat(pos));
            }
        }

        result
    }
}