<?php

namespace xmvc;

use xmvc\Cache;
use MatthiasMullie\Minify;

/**
 * 视图类
 *
<tpl>
    {{../../public/header}}
    {{
        x.table loop=true id="table1" arr=$data values=$data class='w100 blue-table'
        names=[
                "id"=>"ID",
                "name"=>"姓名",
                "age"=>"年龄",
                "sex"=>"性别",
            ]
    }}
    {{admin.public.footer}}
</tpl>

<mount type="js" src="x.test" />
<mount type="css" src="x.test" />

<style>
    @import url(x.table);
</style>

<script>
    import 'x.table';
</script>
 * @version 2.1.0
 */

// @codingStandardsIgnoreStart
class View
// @codingStandardsIgnoreEnd
{
    public Viewer $viewer;
    public bool $isLoop = false;
    public string $name = '';
    public string $shortName = '';
    public string $filename = '';
    public string $mainNodeName = ''; //落在哪一个主节点上
    public array $tagViews = []; //子模板名称数组
    public int $time; //视图生成时间
    public string $contents = '';
    public array $tags = []; // 标签集
    public array $blocks = []; //文件块
    public string $tpl = '';
    public array $staticData = []; //style 和 js 的内容
    public array $cssFiles = []; //css 文件
    public array $jsFiles = []; //js 文件
    public array $includeFiles = []; //所有引入的文件
    public string $cacheFilename = '';
    public function __construct(string $filename, string $name = '')
    {
        $this->name = $name ?: Viewer::getViewName($filename);
        $this->shortName = substr($name, strrpos($name, '.') + 1);
        $this->filename = $filename;
        $this->mainNodeName = $this->getMainNodeName($this->name);
        $this->time = time();
    }

    /**
     * 合并生成视图缓存文件
     */
    public function createCache()
    {
        $this->cacheFilename = Viewer::getViewCacheFilename($this->filename);
        if ($this->cacheFilename == $this->filename) {
            error('缓存文件路径 ' . $this->cacheFilename . ' 不能与  ' . $this->filename . '模板文件路径相同');
        }

        // 合并子模板tpl内容
        $this->mergeTagViewTpl();

        if (!$this->isLoop) {
            // 合并style,javascript
            $this->mergeTagViewScript();

            // 插入脚本的 html 标签
            $this->insertScriptHtmlTag();
        }

        // 写 HTML
        if (!DEBUG || MVC::$cfg['cache']['useHtmlMini']) {
            $this->tpl = $this->minifyHtmlAndPhp($this->tpl); //最小化 HTML
        } else {
            $this->tpl = preg_replace('/^ {4}/m', '', $this->tpl);
        }
        mkdirs(dirname($this->cacheFilename));
        file_put_contents_lock($this->cacheFilename, trim($this->tpl));

        //记录所有使用到的文件及缓存创建时间
        if ($this->includeFiles) {
            $cache = Cache::create(cfg('cache>group>viewtimes>type'), 'viewtimes', cfg('cache>group>viewtimes>configKey'));
            $cache->set($this->name, $this->includeFiles)->save();
        }
    }

    /**
     * 插入脚本的 html 标签
     */
    public function insertScriptHtmlTag()
    {
        if ($this->cssFiles || $this->jsFiles) {
            $linkTag = '';
            $scriptTag = '';
            $pos = mb_strpos($this->tpl, '</head>'); //如果存在 </head> 加在</head>之前
            $myVer = '?ver=' . $this->time;
            foreach ($this->cssFiles as $mainNodeName => $file) {
                $xName = $pos === false ? ' x-name="' . $mainNodeName . '"' : '';
                $ver = substr($mainNodeName, 0, 6) == 'mount.' ? '?ver=' . SVER : $myVer;
                $linkTag .= '<link' . $xName . ' rel="stylesheet" href="' . URL_STATIC . $file . $ver . '" />' . "\r\n";
            }
            foreach ($this->jsFiles as $mainNodeName => $file) {
                $xName = $pos === false ? ' x-name="' . $mainNodeName . '"' : '';
                $ver = substr($mainNodeName, 0, 6) == 'mount.' ? '?ver=' . SVER : $myVer;
                $scriptTag .= '<script' . $xName . ' src="' . URL_STATIC . $file . $ver . '"></script>' . "\r\n";
            }
            if ($pos !== false) {
                $this->tpl = substr($this->tpl, 0, $pos) . $linkTag . $scriptTag . substr($this->tpl, $pos);
            } else {
                $this->tpl =  $linkTag . $scriptTag . $this->tpl;
            }
        }
    }

    /**
     * 处理所有的 style 和 jsavascript 脚本内容
     */
    public function mergeTagViewScript()
    {
        $staticData = [];
        foreach ($this->viewer->list as $view) {
            $this->includeFiles[$view->filename] = $this->time;
            foreach ($view->staticData as $mainNodeName => $nodes) {
                // 将层级最少的排在前面
                uksort($nodes, function ($a, $b) {
                    return substr_count($a, '.') <=> substr_count($b, '.');
                });

                foreach ($nodes as $nodeName => $item) {
                    $staticData[$mainNodeName][$nodeName] = $item;
                }
            }
            foreach ($view->cssFiles as $mainNodeName => $filename) {
                $this->cssFiles[$mainNodeName] = $filename;
            }
            foreach ($view->jsFiles as $mainNodeName => $filename) {
                $this->jsFiles[$mainNodeName] = $filename;
            }
        }
        // 将 x 排在最前面, 将层级最少的排在前面, 也就意味着，后面的层级可以对前面的一些内容做覆盖
        uksort($staticData, function ($a, $b) {
            if ($a === "x" && $b !== "x") {
                return -1;
            } elseif ($a !== "x" && $b === "x") {
                return 1;
            } else {
                return substr_count($a, '.') <=> substr_count($b, '.');
            }
        });
        foreach ($staticData as $mainNodeName => $nodes) {
            // style
            if ($item->style) {
                /* preg_match_all('/([^\{\}]+)\s*\{([^}]*)\}/', $contents, $ms, PREG_SET_ORDER) 合并相同的CSS*/;
                $this->cssFiles[$mainNodeName] = '/auto/css/' . $mainNodeName . '.css';
                $filename = DIR_STATIC . $this->cssFiles[$mainNodeName];
                if (is_file($filename)) {
                    $contents = file_get_contents($filename);
                } else {
                    $contents = '';
                    mkdirs(dirname($filename));
                }
                foreach ($nodes as $nodeName => $item) {
                    $contents = $this->replaceContentByRemark($nodeName, $item->style, $contents, 'style');
                    if ($contents === false) {
                        Viewer::error($this, '<style>', $filename . ' 文件的 ' . $nodeName . ' 没有一个正确的 --END');
                    }
                }
                $contents = $this->parseStyle($contents);
                file_put_contents_lock($filename, $contents);
            }

            // javascript
            if ($item->javascript) {
                $this->jsFiles[$mainNodeName] = '/auto/js/' . $mainNodeName . '.js';
                $filename = DIR_STATIC . $this->jsFiles[$mainNodeName];
                if (is_file($filename)) {
                    $contents = file_get_contents($filename);
                } else {
                    $contents = '';
                    mkdirs(dirname($filename));
                }
                foreach ($nodes as $nodeName => $item) {
                    $contents = $this->replaceContentByRemark($nodeName, $item->javascript, $contents, 'javascript');
                    if ($contents === false) {
                        Viewer::error($this, '<script>', $filename . ' 文件的 ' . $nodeName . ' 没有一个正确的 --END');
                    }
                }
                $contents = $this->parseJavascript($contents);
                file_put_contents_lock($filename, $contents);
            }
        }
    }

    /**
     * 合并子模板内容
     */
    public function mergeTagViewTpl()
    {
        if ($this->tagViews) {
            foreach ($this->tagViews as $i => $tag) {
                $subView = $this->viewer->list[$tag->name];
                // 替换文件块
                if ($subView->isLoop === true || $tag->loop === true) {
                    $this->blocks[$tag->blockIndex] = sprintf(
                        '<?php view("%s",%s); ?>',
                        $tag->filename,
                        $this->attrArrayToText($tag->params)
                    );
                } else {
                    //非循环引用,直接引入模板内容
                    $subView->mergeTagViewTpl();
                    $this->blocks[$tag->blockIndex] = trim($subView->tpl);
                }
            }
            // 合并块为模板内容
            $this->tpl = implode("", $this->blocks);
            if (preg_match('/<tpl>(.*?)<\/tpl>/s', $this->tpl, $m)) {
                $this->tpl =  $m[1];
            }
        }
    }

    /**
     * 根据tag的名称取主点名称
     * - 如果 cfg.cache.staticOneFile = true 则style合成一个 index.css, js 合成 一个 index.js
     * - false
     * - 如果第一级是 x , 则直接使用 x
     * - 如果是二级或二级以下(含有文件名)，则使用第一个内容, admin.sys 返回 admin
     * - 如果是三级或以上(含有文件名)，则使用第一个.第二个内容 admin.public.header 返回 admin.public
     */
    public function getMainNodeName(string $tagName): string
    {
        if (MVC::$cfg['cache']['staticOneFile']) {
            return 'index';
        }
        $arr = explode('.', $tagName);
        $count = count($arr);
        if ($arr[0] == 'x') {
            return 'x';
        } elseif ($count > 2) {
            return $arr[0] . '.' . $arr[1];
        } else {
            return $arr[0];
        }
    }

    /**
     * 分析
     */
    public function parse()
    {
        $this->time = time();
        $this->contents = file_get_contents($this->filename); //文件内容
        $this->replaceBase();
        $this->parseTags();
        $this->block(); // 文件分块
        $this->script(); // 文件脚本分析
    }

    /**
     * 分析和构建视图的所有脚本, html,css,js
     *
     * @param stdClass $view
     */
    public function script()
    {
        $style = '';
        $javascript = '';
        if (preg_match('/@loop/', $this->contents, $m)) {
            $this->isLoop = true;
        }
        $pattern = '/(?!<tpl>[\s\S]*?)<style>((?:(?!<\/style>).)*)<\/style>(?![\s\S]*?<\/tpl>)/s';
        if (preg_match($pattern, $this->contents, $m)) {
            $style = trim($m[1]);
        }
        $pattern = '/(?!<tpl>[\s\S]*?)<script>((?:(?!<\/script>).)*)<\/script>(?![\s\S]*?<\/tpl>)/s';
        if (preg_match($pattern, $this->contents, $m)) {
            $javascript = trim($m[1]);
        }

        $pattern = '/<mount\s+\btype\b\s*=[\'\"]([^\'\"]+)[\'\"]\s*\bsrc\b\s*=[\'\"]([^\'\"]+)[\'\"][^>]*>/is';
        if (preg_match_all($pattern, $this->contents, $m)) {
            foreach ((array)$m[1] as $i => $v) {
                $arr = explode('.', $m[2][$i]);
                if ($arr[0] == 'x') {
                    error('&lt;mount&gt; ' . lg('不支持') . ' x. from ' . $this->filename);
                } else {
                    $filename = implode('/', $arr) . '.' . $v;
                }
                if ($v == 'js') {
                    $this->jsFiles['mount.' . $filename] = '/js/' . $filename;
                } elseif ($v == 'css') {
                    $this->cssFiles['mount.' . $filename] = '/css/' . $filename;
                } else {
                    error(lg('未支持的文件类型') . ' ' . $filename . ' from ' . $this->filename);
                }
            }
        }

        if ($style != '' || $javascript != '') {
            $item = new \stdClass();
            $item->style = $style;
            $item->javascript = $javascript;
            $this->staticData[$this->mainNodeName][$this->name] = $item;
        }

        if (!$this->tagViews) {
            if (preg_match('/<tpl>(.*?)<\/tpl>/s', $this->contents, $m)) {
                $this->tpl =  $m[1];
            }
        }
    }


    /**
     * 分析 style
     * @import url(x.table)
     */
    public function parseStyle(string $style)
    {
        if (preg_match_all('/@import\s+url\(([^)]+)\)\s*;\s*/s', $style, $ms)) {
            $importArr = [];
            foreach ($ms[1] as $i => $m) {
                if (!preg_match('/\.css$/i', $m)) {  //不是 .css结尾的
                    $importArr[$m] = $ms[0][$i];
                }
            }
            foreach ($importArr as $k => $v) {
                $arr = explode('.', $k);
                $filename = '';
                if ($arr[0] == 'x') {
                    unset($arr[0]);
                    $filename = DIR_MVC . '/xview/plugin/' . implode('/', $arr) . '.css';
                } else {
                    $filename = DIR_STATIC . '/css/' . implode('/', $arr) . '.css';
                }
                $style = str_replace($v, '', $style);
                $content = file_get_contents($filename);
                $style = $this->replaceContentByRemark('@import ' . $k, $content, $style, 'style', 'INSERT_BEGIN');
                $this->includeFiles[$filename] = $this->time;
            }
        }
        return $style;
    }


    /**
     * 分析 Javascript
     * import 'x.table';
     */
    public function parseJavascript(string $javascript)
    {
        if (preg_match_all('/\bimport\b\s+[\'\"]([^\'\"]+)[\'\"][\s;]*/s', $javascript, $ms)) {
            $importArr = [];
            foreach ($ms[1] as $i => $m) {
                if (!preg_match('/\.js$/i', $m)) {  //不是 .js 结尾的
                    $importArr[$m] = $ms[0][$i];
                }
            }
            foreach ($importArr as $k => $v) {
                $arr = explode('.', $k);
                $filename = '';
                if ($arr[0] == 'x') {
                    unset($arr[0]);
                    $filename = DIR_MVC . '/xview/plugin/' . implode('/', $arr) . '.js';
                } else {
                    $filename = DIR_STATIC . '/js/' . implode('/', $arr) . '.js';
                }
                $javascript = str_replace($v, '', $javascript);
                $content = file_get_contents($filename);
                $javascript = $this->replaceContentByRemark(
                    '@import ' . $k,
                    $content,
                    $javascript,
                    'style',
                    'INSERT_BEGIN'
                );
                $this->includeFiles[$filename] = $this->time;
            }
        }
        return $javascript;
    }

    /**
     * 根据 注释块 替换文本中对应 注释块 相关内容
     */
    public function replaceContentByRemark(
        string $remarkName,
        string $newInfo,
        string $contents,
        string $type,
        string $insertPos = 'INSERT_END'
    ) {
        $remarkBegin = '/*BEGIN ' . $remarkName . '*/';
        $remarkEnd = '/*' . $remarkName . ' END*/';
        if ($type == 'javascript') {
            $newInfo = (new Minify\JS($newInfo))->minify();
        } elseif ($type == 'style') {
            $newInfo = (new Minify\CSS($newInfo))->minify();
        }
        $posBegin = mb_strpos($contents, $remarkBegin);
        $newContent = $remarkBegin . "\r\n" . $newInfo . "\r\n" . $remarkEnd;
        if ($posBegin === false) {
            if ($contents != '') {
                $contents .= "\r\n";
            }
            if ($insertPos == 'INSERT_END') {
                $contents =  $contents . $newContent;
            } else {
                $contents = $newContent . $contents;
            }
        } else {
            $posEnd = mb_strpos($contents, $remarkEnd);
            if ($posEnd !== false) {
                $posEnd = $posEnd + mb_strlen($remarkEnd);
                $contents = mb_substr($contents, 0, $posBegin) . $newContent . mb_substr($contents, $posEnd);
            } else {
                return false;
            }
        }
        return trim($contents);
    }

    /**
     * 分析 文件块
     */
    public function block()
    {
        if ($this->blocks || !$this->tagViews) {
            return; //已处理过的不再处理, 如果没有子模板不处理
        }
        $blockOffset = 0; //文件分块位置
        $blockIndex = 0;   //文件索引
        $tagPosOffset = []; //相同tag名的起始位置累加
        foreach ($this->tagViews as $tag) {
            $offset = $tagPosOffset[$tag->name] ?? 0;
            $pos = mb_strpos($this->contents, $tag->codeText, $offset);
            $len = mb_strlen($tag->codeText);
            $tagPosOffset[$tag->name] = $pos + $len;
            $this->blocks[$blockIndex++] = mb_substr($this->contents, $blockOffset, $pos - $blockOffset);
            $this->blocks[$blockIndex] = mb_substr($this->contents, $pos, $len);
            $tag->blockIndex = $blockIndex;
            $blockIndex++;
            $blockOffset = $pos + $len;
        }
        $contentLenght = mb_strlen($this->contents);
        if ($blockOffset != $contentLenght) {
            $this->blocks[$blockIndex] = mb_substr($this->contents, $blockOffset);
        }
    }

    /**
     * 基础语法替换
     */
    protected function replaceBase()
    {
        $replaceArr = [
            /* 移除 HTML 注释 */
            '/<!--.*?-->\s*/s' => function ($m) {
                return '';
            },

            /* 替换 {{$class ? " class='$class'" : ""}} 为 <? !isset($class) $class=" class='$class' : $class=""; ?> */
            '/\{\{\s*(\$[a-zA-Z_][^\s]*)\s*\?\s*([^\}:]+)\s*:\s*([^\}]+)\s*\}\}/' => function ($m) {
                return sprintf('<? !isset(%s) ? %s=%s : %s=%s; ?>', $m[1], $m[1], ltrim($m[3]), $m[1], $m[2]);
            },
            /* 替换 {{echo 或者 {{=$class ? " class='$class'" : ""}}
            为 <? if(isset($class)) echo " class='$class'"; else echo ""; ?> */
            '/\{\{\s*(=|echo)\s*(\$[a-zA-Z_][^\s]*)\s*\?\s*([^\}:]+)\s*:\s*([^\}]+)\s*\}\}/' => function ($m) {
                return sprintf('<? if(isset(%s)) echo %s; else echo %s; ?>', $m[2], $m[3], ltrim($m[4]));
            },
            /* 替换 {{$id ?: 0}} 为 <? !isset($id) && $id=0; ?>*/
            '/\{\{\s*(\$[a-zA-Z_][^\s]*)\s*\?\:\s*([^\}]+)\s*\}\}/' => function ($m) {
                return sprintf('<? !isset(%s) && %s=%s; ?>', $m[1], $m[1], $m[2]);
            },
            /* 替换 {{echo 或者 {{=$id ?: 0}} 为 <? if(isset($id)) echo $id; else echo 0; ?> */
            '/\{\{\s*(=|echo)\s*(\$[a-zA-Z_][^\s]*)\s*\?\:\s*([^\}]+)\s*\}\}/' => function ($m) {
                return sprintf('<? if(isset(%s)) echo %s; else echo %s; ?>', $m[2], $m[2], $m[3]);
            },
            /* 替换 {{$css}} 为 <?= isset($css) ? $css : ''?>*/
            '/\{\{\s*(\$)*([a-zA-Z_][a-z_A-Z0-9\-\>\'\"\[\]]*)\s*\}\}/' => function ($m) {
                if ($m[1] != '') { //变量
                    return '<? echo isset($' . $m[2] . ') ? $' . $m[2] . ' : "" ?>';
                } else { //常量
                    return '<? echo ' . $m[2] . ' ?>';
                }
            },
            // 替换 <? 短标签为长标签 <?php
            '/<\?(?!php\s)(?!=)\s*?/' => function ($m) {
                return '<?php';
            }
        ];
        $this->contents = preg_replace_callback_array($replaceArr, $this->contents);
    }

    /**
     * 分析 {{  }} 中的内容并返回结果
     */
    protected function parseTags()
    {
        $idx = 0;
        $partern = '/\{\{(.*?)\}\}/s';
        if (preg_match_all($partern, $this->contents, $m)) {
            foreach ($m[1] as $i => $v) {
                $codeText = $m[0][$i];
                if (trim($v) == '') {
                    continue;
                }
                $pattern = '/^[\s]*([a-zA-Z0-9_\.\/]*)(?: |$)/';
                if (preg_match($pattern, $v, $m2)) {
                    $tag = new \stdClass();
                    $tag->name = $m2[1];
                    $tag->codeText = $codeText;
                    $tag->params = [];
                    $paramText = mb_substr($v, mb_strlen($m2[0]));
                    if ($paramText != '') {
                        $tag->params = $this->parseAttributes($paramText);
                    }
                    $tag->loop = isset($tag->params['loop'])
                                    && filter_var($tag->params['loop'], FILTER_VALIDATE_BOOLEAN);
                    if (strpos($tag->name, '.') !== false) {
                        // tag 中含有.点号即子视图模板
                        $tag->type = 'view';
                        $tag->filename = Viewer::getFilenameByViewName($tag->name);
                        $tag->name = Viewer::getViewName($tag->filename);
                        $this->tagViews[] = $tag;
                    } else {
                        $this->tags[$idx] = $tag;
                        $idx++;
                    }
                }
            }
        }
    }

    /**
     * 标签的属性数组转php代码文本
     *
     * @param array $params ['attrs'=>$attrs, 'types'=>$types]
     * @return string
     */
    public static function attrArrayToText(array $params): string
    {
        if (!$params) {
            return 'null';
        }
        $texts = [];
        foreach ($params as $name => $text) {
            $texts[] = "'" . $name . "' => " . $text;
        }
        return '[' . implode(", ", $texts) . ']';
    }

    /**
     * 标签的属性文本转数组
     *
     * @param string $string
     */
    protected function parseAttributes(string $string): array
    {
        $attrs = [];
        $string = trim($string);
        $currentKey = null;
        $currentValue = '';
        $inQuotes = false; //是否在引号内
        $inBrackets = false; //是否在中括号内
        $equal = false; //等号+空格块,忽略 = 号的空格
        $depth = 0;
        for ($i = 0; $i < strlen($string); $i++) {
            $char = $string[$i];
            if ($equal && ($char === ' ' || $char === "\n")) {
                //忽略 = 号后面的空格
            } elseif (($char === ' ' || $char === ',' || $char === "\n") && !$inQuotes && !$inBrackets) {
                $equal = false;
                // 遇到空格且不在引号或中括号内，可能是键和值的分隔符
                if ($currentKey !== null) {
                    $currentValue = trim($currentValue);
                    $attrs[$currentKey] = $currentValue;
                    $currentKey = null;
                    $currentValue = '';
                }
            } elseif ($char === '=' && !$inQuotes && !$inBrackets) {
                // 遇到等号且不在引号或中括号内，可能是键和值的开始
                $equal = true;
                $currentKey = $currentValue;
                $currentValue = '';
            } elseif (($char === '"' || $char === "'") && !$inBrackets) {
                $equal = false; //等号+空格结果
                // 遇到引号且不在中括号内，开始或结束一个被引号包围的值
                $inQuotes = !$inQuotes;
                $currentValue .= $char;
            } elseif ($char === '[') {
                $equal = false; //等号+空格结果
                // 遇到左中括号，开始一个被中括号包围的值
                if (!$inQuotes) {
                    $inBrackets = true;
                    $depth++;
                }
                $currentValue .= $char;
            } elseif ($char === ']') {
                // 遇到右中括号，结束一个被中括号包围的值
                if (!$inQuotes) {
                    $depth--;
                    if ($depth === 0) {
                        $inBrackets = false;
                    }
                }
                $currentValue .= $char;
            } elseif (!$inQuotes && !$inBrackets) {
                $equal = false;
                // 不在引号或中括号内，直接添加字符到当前值
                $currentValue .= $char;
            } elseif ($inQuotes) {
                $equal = false;
                $currentValue .= $char;
            } elseif ($inBrackets) {
                $equal = false;
                $currentValue .= $char;
            }
        }

        // 处理最后一个键值对
        if ($currentKey !== null) {
            $attrs[$currentKey] = $currentValue;
        }

        // 处理中括号内的内容（如果需要的话）
        /*
        foreach ($result as $key => $value) {
            if (strpos($value, '[') !== false) {
                $result[$key] = parseAttributes($value); // 递归调用解析器
            }
        }*/

        return $attrs;
    }

    /** 最小化模板代码 */
    public function minifyHtmlAndPhp($content)
    {
        // 移除 HTML 注释， 在前面已经做了
        //$content = preg_replace('/<!--.*?-->/', '', $content);

        // 移除 PHP 注释
        $content = preg_replace('/<\?php\s*\/\*(.*?)\*\/\s*\?>/s', '', $content);
        $content = preg_replace('/<\?php\s*\/\/(.*?)\s*\?>/s', '', $content);
        // 移除 HTML 标签之间的空格，但保留在 PHP 代码内的内容
        $content = preg_replace_callback('/(<\?php.*?\?>)|>\s+</', function ($matches) {
            if (!empty($matches[1])) {
                // PHP 代码内的内容
                return $matches[0];
            } else {
                // HTML 标签之间的空格
                return '><';
            }
        }, $content);
        // 移除多余的空格、制表符、换行符，但保留在双引号或单引号内的空格、制表符、换行符
        $parttern = '/("[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\')|(\s)+/';
        $content = preg_replace_callback($parttern, function ($matches) {
            if (!empty($matches[1])) {
                // 双引号或单引号内的内容
                return $matches[0];
            } else {
                // 非双引号或单引号内的空格、制表符、换行符
                return ' ';
            }
        }, $content);
        return $content;
    }
}
