PHP - preg_replace_callback для CamelCasing - PullRequest
1 голос
/ 03 октября 2019

У меня есть следующее содержимое

"aa_bb" : "foo"
"pp_Qq" : "bar"
"Xx_yY_zz" : "foobar"

И я хочу преобразовать содержимое с левой стороны в camelCase

"aaBb" : "foo"
"ppQq" : "bar"
"xxYyZz" : "foobar"

И код:

// selects the left part
$newString = preg_replace_callback("/\"(.*?)\"(.*?):/", function($matches) {        
    // selects the characters following underscores
    $matches[1] = preg_replace_callback("/_(.?)/", function($matches) {
        //removes the underscore and uppercases the character
        return strtoupper($matches[1]);
    }, $matches[1]);

    // lowercases the first character before returning
    return "\"".lcfirst($matches[1])."\" : ".$matches[2];
}, $string);

Можно ли упростить этот код?

Примечание. Содержимое всегда будет представлять собой одну строку.

Ответы [ 2 ]

1 голос
/ 07 октября 2019

Во-первых, поскольку у вас уже есть рабочий код, который вы хотите улучшить, рассмотрите возможность размещения вашего вопроса в обзоре кода вместо stackoverflow в следующий раз.

Давайте начнем улучшать ваш оригинальный подход:

$result = preg_replace_callback('~"[^"]*"\s*:~', function ($m) {
    return preg_replace_callback('~_+(.?)~', function ($n) {
        return strtoupper($n[1]);
    }, strtolower($m[0]));
}, $str);

pro: шаблоны относительно просты, и идея проста для понимания.
минусов: вложенных preg_replace_callback могут повредитьглаза.

После этого упражнения для разогрева глаз мы можем попробовать \G подход на основе шаблона:

$pattern = '~(?|\G(?!^)_([^_"]*)|("(?=[^"]*"\s*:)[^_"]*))~';
$result = preg_replace_callback($pattern, function ($m) {
    return ucfirst(strtolower($m[1]));
}, $str);

pro: кодкороче, не нужно использовать два preg_replace_callback.
минусов: шаблон гораздо более сложный.

примечание: когда вы пишете длинный шаблон,ничто не запрещает использовать свободный интервал с модификатором x и оставлять комментарии:

$pattern = '~
(?| # branch reset group: in which capture groups have the same number
    \G # contigous to the last successful match
    (?!^) # but not at the start of the string    
    _
    ( [^_"]* ) # capture group 1
  |
    ( # capture group 1
        "
        (?=[^"]*"\s*:) # lookahead to check if it is the "key part"
        [^_"]*
    )
)
~x';

Есть ли компромиссы между этими двумя крайностями, и что является хорошим? Два предложения:

$result = preg_replace_callback('~"[^"]+"\s*:~', function ($m) {
    return array_reduce(explode('_', strtolower($m[0])), function ($c, $i) {
        return $c . ucfirst($i);
    });
}, $str);

pro: минимальное использование регулярных выражений.
cons: нужны две функции обратного вызова, за исключением того, что на этот раз вторая вызываетсяarray_reduce и не preg_replace_callback.

$result = preg_replace_callback('~["_][^"_]*(?=[^"]*"\s*:)~', function ($m) {
    return ucfirst(strtolower(ltrim($m[0], '_')));
}, $str);

pro: шаблон относительно прост, и функция обратного вызова также остается простой. Это выглядит как хороший компромисс.
минусы: шаблон не очень сужен (но должен подойти для вашего случая использования)

описание шаблона: шаблон ищет_ или a "и соответствует следующим символам, которые не являются _ или a". Затем предварительное утверждение проверяет, что эти символы находятся внутри ключевой части в поисках закрывающей кавычки и двоеточия. Результат совпадения всегда выглядит как _aBc или "aBc (подчеркивания обрезаются слева в функции обратного вызова и " остается неизменным после применения ucfirst).

детали шаблона:

["_] # one " or _
[^"_]* # zero or more characters that aren't " or _
(?= # open a lookahead assertion (followed with)
    [^"]* # all that isn't a "
    " # a literal "
    \s* # eventual whitespaces
    : # a literal :
) # close the lookahead assertion

Нет хорошего ответа, и то, что выглядит простым или сложным, действительно зависит от читателя.

1 голос
/ 03 октября 2019

Вы можете использовать preg_replace_callback в сочетании с \G группами привязки и захвата.

(?:"\K([^_\r\n]+)|\G(?!^))(?=[^":\r\n]*")(?=[^:\r\n]*:)_?([a-zA-Z])([^"_\r\n]*)

По частям

  • (?:Группа без захвата
    • "\K([^_\r\n]+) Совпадение ", захват группа 1 совпадение 1+ раз с любым символом, кроме _ или новой строки
    • | Или
    • \G(?!^) Утверждение позиции в предыдущем матче, а не в начале
  • ) Закрытие группы
  • (?=[^":\r\n]*") Позитивный прогноз, подтверждение "
  • (?=[^:\r\n]*:) Позитивный взгляд, утверждаю :
  • _? Соответствие необязательно _
  • ([a-zA-Z]) Захват группа 2 соответствие a-zA-Z
  • ([^"_\r\n]*) Захват группа 3 совпадение 0+ раз с любым символом, кроме _ или новой строки

При замене объединить комбинацию strtolower и strtoupper с использованием 3 групп захвата.

Regex demo

Например

$re = '/(?:"\K([^_\r\n]+)|\G(?!^))(?=[^":\r\n]*")(?=[^:\r\n]*:)_?([a-zA-Z])([^"_\r\n]*)/';
$str = '"aa_bb" : "foo"

"pp_Qq" : "bar"

"Xx_yY_zz" : "foobar"
"Xx_yYyyyyyYyY_zz_a" : "foobar"';

$result =  preg_replace_callback($re, function($matches) {
    return strtolower($matches[1]) . strtoupper($matches[2]) . strtolower($matches[3]);
}, $str);

echo $result;

Вывод

"aaBb" : "foo"

"ppQq" : "bar"

"xxYyZz" : "foobar"
"xxYyyyyyyyyyZzA" : "foobar"

Php demо

...