소스 검색

修复json格式化反转义问题,正则表达式支持可视化测试

zxlie 4 달 전
부모
커밋
9c17e58a43
7개의 변경된 파일297개의 추가작업 그리고 10개의 파일을 삭제
  1. 1 0
      apps/json-format/format-lib.js
  2. 2 0
      apps/json-format/index.html
  3. 41 1
      apps/json-format/index.js
  4. 14 3
      apps/json-format/json-worker.js
  5. 107 1
      apps/regexp/index.css
  6. 16 0
      apps/regexp/index.html
  7. 116 5
      apps/regexp/index.js

+ 1 - 0
apps/json-format/format-lib.js

@@ -140,6 +140,7 @@ window.Formatter = (function () {
         str = str.replace(/>/g, '>');
         str = str.replace(/"/g, '"');
         str = str.replace(/'/g, ''');
+        str = str.replace(/\\/g, '\');
         return str;
     };
 

+ 2 - 0
apps/json-format/index.html

@@ -37,6 +37,8 @@
                 <span class="x-split">|</span>
                 <input type="checkbox" v-model="overrideJson" id="jsonOvrd" @click="setCache"><label for="jsonOvrd">节点编辑</label>
                 <span class="x-split">|</span>
+                <input type="checkbox" v-model="autoUnpackJsonString" id="autoUnpackJsonString" @click="autoUnpackJsonStringFn"><label for="autoUnpackJsonString">支持嵌套解析</label>
+                <span class="x-split">|</span>
                 <span class="x-sort">
                     <span class="x-stitle">排序:</span>
                     <label for="sort_null">默认</label>

+ 41 - 1
apps/json-format/index.js

@@ -24,7 +24,8 @@ new Vue({
         autoDecode: false,
         fireChange: true,
         overrideJson: false,
-        isInUSAFlag: false
+        isInUSAFlag: false,
+        autoUnpackJsonString: false
     },
     mounted: function () {
         // 自动开关灯控制
@@ -153,6 +154,12 @@ new Vue({
                 // 这里什么动作都不需要做,这种情况下转换失败的,肯定是Value被污染了,抛弃即可
             }
 
+            // 新增:自动解包嵌套JSON字符串
+            if (this.autoUnpackJsonString && jsonObj != null && typeof jsonObj === 'object') {
+                jsonObj = deepParseJSONStrings(jsonObj);
+                source = JSON.stringify(jsonObj);
+            }
+
             // 是json格式,可以进行JSON自动格式化
             if (jsonObj != null && typeof jsonObj === "object" && !this.errorMsg.length) {
                 try {
@@ -322,7 +329,40 @@ new Vue({
             this.$nextTick(() => {
                 this.format();
             })
+        },
+
+        autoUnpackJsonStringFn: function () {
+            this.$nextTick(() => {
+                localStorage.setItem('jsonformat:auto-unpack-json-string', this.autoUnpackJsonString);
+                this.format();
+            });
         }
     }
 });
 
+// 新增:递归解包嵌套JSON字符串的函数
+function deepParseJSONStrings(obj) {
+    if (Array.isArray(obj)) {
+        return obj.map(deepParseJSONStrings);
+    } else if (typeof obj === 'object' && obj !== null) {
+        const newObj = {};
+        for (const key in obj) {
+            if (!obj.hasOwnProperty(key)) continue;
+            const val = obj[key];
+            if (typeof val === 'string') {
+                try {
+                    const parsed = JSON.parse(val);
+                    // 只递归对象或数组
+                    if (typeof parsed === 'object' && parsed !== null) {
+                        newObj[key] = deepParseJSONStrings(parsed);
+                        continue;
+                    }
+                } catch (e) {}
+            }
+            newObj[key] = deepParseJSONStrings(val);
+        }
+        return newObj;
+    }
+    return obj;
+}
+

+ 14 - 3
apps/json-format/json-worker.js

@@ -151,9 +151,15 @@ function createNode(value) {
         getHTML: function() {
             switch(this.type) {
                 case 'string':
-                    return '<div class="item item-line"><span class="string">"' + 
-                        formatStringValue(this.value) + 
-                        '"</span></div>';
+                    // 判断原始字符串是否为URL
+                    if (isUrl(this.value)) {
+                        // 用JSON.stringify保证转义符显示,内容包裹在<a>里
+                        return '<div class="item item-line"><span class="string"><a href="' 
+                            + htmlspecialchars(this.value) + '" target="_blank" rel="noopener noreferrer">' 
+                            + htmlspecialchars(JSON.stringify(this.value)) + '</a></span></div>';
+                    } else {
+                        return '<div class="item item-line"><span class="string">' + formatStringValue(JSON.stringify(this.value)) + '</span></div>';
+                    }
                 case 'number':
                     // 确保大数字不使用科学计数法
                     let numStr = typeof this.value === 'number' && this.value.toString().includes('e') 
@@ -466,5 +472,10 @@ function getType(value) {
         if (Array.isArray(value)) return 'array';
     }
     return type;
+}
+
+function isUrl(str) {
+    const urlRegex = /^(https?:\/\/|ftp:\/\/)[^\s<>"'\\]+$/i;
+    return urlRegex.test(str);
 } 
 

+ 107 - 1
apps/regexp/index.css

@@ -1,4 +1,3 @@
-
 /* 全局样式 */
 * {
     margin: 0;
@@ -459,3 +458,110 @@ pre {
     text-decoration: underline;
     color: #f00;
 }
+
+/* 正则可视化调试区域样式 */
+.regex-visual-debugger {
+    background: #fff;
+    border-radius: 8px;
+    box-shadow: 0 1px 3px rgba(0,0,0,0.08);
+    padding: 1.2rem 1rem 1rem 1rem;
+    margin-bottom: 1.2rem;
+}
+.regex-visual-debugger h2 {
+    font-size: 1.1rem;
+    color: #2563eb;
+    margin-bottom: 0.8rem;
+    border-left: 4px solid #2563eb;
+    padding-left: 0.6rem;
+}
+.visual-debugger-form {
+    display: flex;
+    gap: 0.5rem;
+    margin-bottom: 0.5rem;
+}
+.visual-debugger-form input[type="text"] {
+    flex: 1;
+    padding: 0.5rem 0.8rem;
+    border: 1.5px solid #3498db;
+    border-radius: 4px;
+    font-size: 1rem;
+    background: #f8f9fa;
+    transition: border-color 0.2s;
+}
+.visual-debugger-form input[type="text"]:focus {
+    border-color: #2563eb;
+    background: #fff;
+}
+#visualTestText {
+    width: 100%;
+    min-height: 60px;
+    font-size: 1rem;
+    border: 1.5px solid #e9ecef;
+    border-radius: 4px;
+    padding: 0.6rem 0.8rem;
+    margin-bottom: 0.5rem;
+    background: #f8f9fa;
+    resize: vertical;
+    transition: border-color 0.2s;
+}
+#visualTestText:focus {
+    border-color: #2563eb;
+    background: #fff;
+}
+.visual-debugger-actions {
+    display: flex;
+    align-items: center;
+    gap: 1rem;
+    margin-bottom: 0.5rem;
+}
+#visualTestBtn {
+    background: #3498db;
+    color: #fff;
+    border: none;
+    border-radius: 4px;
+    padding: 0.4rem 1.2rem;
+    font-size: 1rem;
+    cursor: pointer;
+    transition: background 0.2s;
+}
+#visualTestBtn:hover {
+    background: #2563eb;
+}
+.visual-error-msg {
+    color: #e74c3c;
+    font-size: 0.95rem;
+    min-width: 120px;
+}
+.visual-result {
+    background: #f8f9fa;
+    border: 1px solid #e9ecef;
+    border-radius: 4px;
+    min-height: 36px;
+    padding: 0.7rem 0.8rem;
+    font-size: 1rem;
+    color: #222;
+    margin-top: 0.2rem;
+    word-break: break-all;
+    white-space: pre-wrap;
+}
+.visual-result .visual-match {
+    background: #fff3cd;
+    color: #d35400;
+    border-radius: 3px;
+    padding: 0 2px;
+    font-weight: bold;
+}
+
+.visual-group-list {
+    display: block;
+    margin-top: 2px;
+    font-size: 0.92em;
+}
+.visual-group {
+    background: #e3f6fc;
+    color: #0077b6;
+    border-radius: 2px;
+    margin-right: 4px;
+    padding: 0 3px;
+    font-weight: normal;
+}

+ 16 - 0
apps/regexp/index.html

@@ -18,6 +18,22 @@
         </header>
 
         <main class="container">
+            
+            <!-- 正则可视化调试区域 -->
+            <section class="regex-visual-debugger">
+                <h2>正则表达式可视化调试</h2>
+                <div class="visual-debugger-form">
+                    <input type="text" id="visualRegex" placeholder="请输入正则表达式,如:^\\d{3}-\\d{8}$" />
+                    <input type="text" id="visualFlags" placeholder="标志(可选,如:gim)" maxlength="5" />
+                </div>
+                <textarea id="visualTestText" rows="4" placeholder="请输入要测试的文本"></textarea>
+                <div class="visual-debugger-actions">
+                    <button id="visualTestBtn">测试匹配</button>
+                    <span id="visualErrorMsg" class="visual-error-msg"></span>
+                </div>
+                <div id="visualResult" class="visual-result"></div>
+            </section>
+            
             <div class="search-container">
                 <div class="search-box">
                     <input type="text" id="regexSearch" placeholder="搜索正则表达式..." />

+ 116 - 5
apps/regexp/index.js

@@ -73,12 +73,12 @@ const regexDatabase = {
     },
     'url': {
         title: 'URL验证',
-        description: '验证URL地址的合法性,支持http、https协议',
+        description: '验证URL地址的合法性,支持http、https协议,可选端口、路径、参数、锚点,支持localhost和IP地址',
         patterns: {
-            javascript: '/^(https?:\\/\\/)?([\\da-z.-]+)\\.([a-z.]{2,6})([\\/\\w .-]*)*\\/?$/',
-            python: 'r"^(https?:\\/\\/)?([\\da-z.-]+)\\.([a-z.]{2,6})([\\/\\w .-]*)*\\/?$"',
-            php: '/^(https?:\\/\\/)?([\\da-z.-]+)\\.([a-z.]{2,6})([\\/\\w .-]*)*\\/?$/',
-            java: '^(https?:\\/\\/)?([\\da-z.-]+)\\.([a-z.]{2,6})([\\/\\w .-]*)*\\/?$'
+            javascript: '/^(https?:\\/\\/)?((([\\w-]+\\.)+[\\w-]+|localhost|\\d{1,3}(?:\\.\\d{1,3}){3}))(\\:\\d{1,5})?(\\/[^\\s?#]*)?(\\?[^\\s#]*)?(#[^\\s]*)?$/i',
+            python: 'r"^(https?:\/\/)?((([\w-]+\.)+[\w-]+|localhost|\d{1,3}(?:\.\d{1,3}){3}))(\:\d{1,5})?(\/[^s?#]*)?(\?[^s#]*)?(#[^s]*)?$"',
+            php: '/^(https?:\\/\\/)?((([\\w-]+\\.)+[\\w-]+|localhost|\\d{1,3}(?:\\.\\d{1,3}){3}))(\\:\\d{1,5})?(\\/[^\\s?#]*)?(\\?[^\\s#]*)?(#[^\\s]*)?$/i',
+            java: '^(https?:\\/\\/)?((([\\w-]+\\.)+[\\w-]+|localhost|\\d{1,3}(?:\\.\\d{1,3}){3}))(\\:\\d{1,5})?(\\/[^\\s?#]*)?(\\?[^\\s#]*)?(#[^\\s]*)?$'
         }
     },
     'idcard': {
@@ -628,3 +628,114 @@ document.addEventListener('DOMContentLoaded', function() {
         }
     });
 });
+
+// 正则可视化调试区域逻辑(升级版)
+function parsePatternAndFlags(input) {
+    // 自动识别 /pattern/flags 或 pattern
+    const match = input.match(/^\s*\/(.*)\/(\w*)\s*$/);
+    if (match) {
+        return { pattern: match[1], flags: match[2] };
+    }
+    return { pattern: input, flags: '' };
+}
+
+function highlightMatchesV2(text, regex) {
+    if (!text) return '';
+    let lastIndex = 0;
+    let result = '';
+    let match;
+    let hasMatch = false;
+    regex.lastIndex = 0;
+    let count = 0;
+    while ((match = regex.exec(text)) !== null) {
+        hasMatch = true;
+        count++;
+        // 防止死循环
+        if (match[0] === '') {
+            result += text.slice(lastIndex);
+            break;
+        }
+        result += text.slice(lastIndex, match.index);
+        // 高亮主匹配内容
+        let main = '<span class="visual-match">' + match[0] + '</span>';
+        // 如果有分组,显示分组高亮
+        if (match.length > 1) {
+            let groupHtml = '';
+            for (let i = 1; i < match.length; i++) {
+                if (typeof match[i] === 'string') {
+                    groupHtml += `<span class="visual-group">$${i}: ${match[i]}</span> `;
+                }
+            }
+            main += '<span class="visual-group-list">' + groupHtml.trim() + '</span>';
+        }
+        result += main;
+        lastIndex = match.index + match[0].length;
+        if (!regex.global) break;
+    }
+    result += text.slice(lastIndex);
+    return { html: hasMatch ? result : text, count };
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+    // 可视化调试相关
+    const visualRegexInput = document.getElementById('visualRegex');
+    const visualFlagsInput = document.getElementById('visualFlags');
+    const visualTestText = document.getElementById('visualTestText');
+    const visualTestBtn = document.getElementById('visualTestBtn');
+    const visualResult = document.getElementById('visualResult');
+    const visualErrorMsg = document.getElementById('visualErrorMsg');
+
+    function doVisualTest() {
+        let pattern = visualRegexInput.value.trim();
+        let flags = visualFlagsInput.value.trim();
+        // 自动识别 /pattern/flags
+        if (pattern.startsWith('/') && pattern.lastIndexOf('/') > 0) {
+            const parsed = parsePatternAndFlags(pattern);
+            pattern = parsed.pattern;
+            if (!flags) flags = parsed.flags;
+        }
+        const testText = visualTestText.value;
+        visualErrorMsg.textContent = '';
+        visualErrorMsg.style.visibility = 'hidden';
+        visualResult.innerHTML = '';
+        if (!pattern) {
+            visualErrorMsg.textContent = '请输入正则表达式';
+            visualErrorMsg.style.visibility = 'visible';
+            return;
+        }
+        let regex;
+        try {
+            regex = new RegExp(pattern, flags);
+        } catch (e) {
+            visualErrorMsg.textContent = '正则表达式有误:' + e.message;
+            visualErrorMsg.style.visibility = 'visible';
+            return;
+        }
+        // 匹配并高亮
+        const { html, count } = highlightMatchesV2(testText, regex);
+        visualResult.innerHTML = html;
+        // 匹配次数提示
+        if (pattern && testText) {
+            const tip = document.createElement('div');
+            tip.style.margin = '8px 0 0 0';
+            tip.style.color = '#2563eb';
+            tip.style.fontSize = '0.98rem';
+            tip.textContent = `共匹配 ${count} 处`;
+            visualResult.appendChild(tip);
+        }
+    }
+
+    visualTestBtn.addEventListener('click', doVisualTest);
+    // 支持回车快捷测试
+    [visualRegexInput, visualFlagsInput, visualTestText].forEach(el => {
+        el.addEventListener('keydown', function(e) {
+            if (e.key === 'Enter' && (el !== visualTestText || e.ctrlKey || e.metaKey)) {
+                doVisualTest();
+                e.preventDefault();
+            }
+        });
+    });
+
+    // textarea内容变化时自动调试
+    visualTestText.addEventListener('input', doVisualTest);
+});