Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Регулярное выражение с игнорированием символа в кавычках (https://javascript.ru/forum/misc/77775-regulyarnoe-vyrazhenie-s-ignorirovaniem-simvola-v-kavychkakh.html)

Tachyon 19.06.2019 15:53

Регулярное выражение с игнорированием символа в кавычках
 
Здравствуйте. Прошу помочь мне в составлении регулярного выражения (или сообщить, если решение этой задачи невозможно в рамках регулярных выражений JS).

Задача: нужно распарсить код, по структуре похожий на HTML. Пример:
<tag1 name1="value1" name2 = 'value2'>
< tag1 name1="value1"name2 = 'value2' >

Наличие и расположение пробелов может быть произвольным. Кавычки могут быть одинарными или двойными.

Требуется: выбрать весь тег, начиная с открывающего символа < и заканчивая закрывающим > включительно.

Задача осложняется тем, что значения, заключенные в кавычки, могут содержать символы < и >. Следовательно, выражение типа
/<\s*tag1([^>]*)>/gi

Не подходит.

Можно ли как-нибудь выбрать текст до первого не заключенного в кавычки (одинарные или двойные) символа > при помощи регулярного выражения, или нужно писать парсер с логическим анализом?

рони 19.06.2019 16:27

Tachyon,
вариант...
let str = `<tag1 name1="value1" name2 = 'value>>>>><<<2'>
 < tag1 name1="value1"name2 = 'value2<<>>>' >`;
let reg = /<\s*?\S+((['"])[^'"]*\2|[^>])*>/gi;
console.log(str.match(reg))

Белый шум 19.06.2019 19:37

Подправил маленько
let str = `<tag1 name1="<value1's>" name2 = 'value>>>>><<<2'>
 < tag1 name1="value1"name2 = 'value2<<>>>' >`;
let reg = /<\s*?\S+((['"]).*?\2|[^>])*>/gi;
console.log(str.match(reg))

Alexandroppolus 19.06.2019 21:17

Представленные варианты ошибочно срабатывают на
str = `<tag2 c="" d=">`;

и, что самое грустное, безбожно зависают на длинном незакрытом теге
str = `<tag1 b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""b=""`;


Вот такое вроде бы нигде не косячит:
let str = `<tag1 name1="<value1's>" name2 = 'value>>>>><<<2'>
 < tag1 name1="value1"name2 = 'value2<<>>>' >`;

let reg = /<\s*\w+(?:"[^"]*"|'[^']*'|[^>"'])*>/gi;

console.log(str.match(reg));

Tachyon 20.06.2019 09:49

Большое спасибо! Немного усложнил последний вариант (на случай отсутствия параметров в теге) и погонял его с разными комбинациями, которые смог придумать, вроде бы везде сработал корректно:
var string = `aaaa< tag1 name1=">><<" name2= "val'2"name3 = 'val><"3' name4 ='' >bbbb`;
string = string.replace(/<\s*tag1(\s+(?:"[^"]*"|'[^']*'|[^>"'])*)?>/gi, function(a,b) {
		alert(b);
		return '';
	}
);
alert(string);


Задам еще один вопрос. Я правильно понимаю, что в регулярных выражениях JS отсутствует опережающая проверка, и из-за этого обработку тегов типа
<tag1 name='value'>string'"test<>continue</tag1>
с выбором строки не до символа, а до слова /<\s*\/\s*tag1\s*>/ регулярным выражением не сделать?

Malleys 20.06.2019 16:24

Цитата:

Сообщение от Alexandroppolus
Вот такое вроде бы нигде не косячит:

А такое тоже должно подходить?
Код:

<tag1""""""""""""""""""/>
Цитата:

Сообщение от Tachyon
погонял его с разными комбинациями, которые смог придумать, вроде бы везде сработал корректно:

А такое тоже должно подходить?
Код:

<tag1""""""""""""""""""/>
<tag1 %ЭПОХА% @ВЫМЫСЛА@>

Цитата:

Сообщение от Tachyon
выбрать весь тег, начиная с открывающего символа < и заканчивая закрывающим > включительно.

Задача осложняется тем, что значения, заключенные в кавычки, могут содержать символы < и >.

/<\s*tag1(?:\s*[^\s=>]+\s*=\s*(?:'(?:\\\\|\\'|[^\\'])*'|"(?:\\\\|\\"|[^\\"])*"))*\s*>/gi
Как оно выглядит?

Tachyon 20.06.2019 17:23

Цитата:

Сообщение от Malleys (Сообщение 509266)
А такое тоже должно подходить?

По идее не должно. Должны быть только варианты имя=значение, или имя:значение. Вокруг = (или : ) могут быть пробелы. Значение может быть в одинарных или двойных кавычках, может быть без кавычек, если не содержит пробелов и других не буквенно-цифровых символов.

Чтобы парсить полученную строку с параметрами я составил вот такой regexp, вроде бы делает именно то, что надо:
/\s*([\w-]+)\s*[=:]\s*("[^"]*"|'[^']*'|[\w-]+)\s*/g


Единственный недостаток - могут встречаться ключи без значения. Типа <tag1 name='value' clear>
Подразумевается, что clear=true. Но ретроспективные и опережающие проверки на IE 11 и FF 56, видимо, совсем не работают. Так что я сделаю replace по предыдущему выражению, чтобы удалило из строки все найденные пары ключ=значение, а что останется - буду уже парсить отдельно.

Malleys 20.06.2019 17:51

Цитата:

Сообщение от Tachyon
Должны быть только варианты имя=значение, или имя:значение. Вокруг = (или : ) могут быть пробелы. Значение может быть в одинарных или двойных кавычках, может быть без кавычек, если не содержит пробелов и других не буквенно-цифровых символов.

Цитата:

Сообщение от Tachyon
могут встречаться ключи без значения. Типа <tag1 name='value' clear>

Можно так...

/<\s*tag1(?:\s*(?:[\w-]+)\s*(?:[:=]\s*(?:'(?:\\\\|\\'|[^\\'])*'|"(?:\\\\|\\"|[^\\"])*"|[\w-]+))?)*\s*>/gi
Как это выглядит?

Alexandroppolus 20.06.2019 19:25

Цитата:

Сообщение от Tachyon
Задам еще один вопрос. Я правильно понимаю, что в регулярных выражениях JS отсутствует опережающая проверка, и из-за этого обработку тегов типа
<tag1 name='value'>string'"test<>continue</tag1>
с выбором строки не до символа, а до слова /<\s*\/\s*tag1\s*>/ регулярным выражением не сделать?

В принципе можно при определенных ограничениях, только регулярки охренительные будут. Вон для открывающего тега уже черт знает что пришлось нагородить :) А тут много всяких кейсов добавляется: это и вложенные теги, и подстрока "</tag1>" в атрибуте постороннего тега, а если ещё комментарии <!-- --> добавятся, то всё, приехали. Так что в общем случае надо лепить что-то вроде конечного автомата на регулярках и честно обходить всю строку целиком.

Цитата:

Сообщение от Malleys
Можно так...

/<\s*tag1(?:\s*(?:[\w-]+)\s*(?:(=)\s*(?:'(?:\\\\|\\'|[^\\'])*'|"(?:\\\\|\\"|[^\\"])*"|[\w-]+))?)*\s*>/gi

Та же засада с бэктрекингом для строки `<tag1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`


Немного пофиксил, на этот раз вроде всё как надо :)
/<\s*tag1(?:\s+(?:[\w-]+(?![\w-])(?:\s*[:=]\s*(?:'(?:\\\\|\\'|[^\\'])*'|"(?:\\\\|\\"|[^\\"])*"|[\w-]+))?\s*)+)?\s*>/gi

Malleys 20.06.2019 21:02

Alexandroppolus, замечательно!

Tachyon, меня интересует, почему всё-таки нужно такое? Почему бы не использовать XML? Тогда не нужно так мучаться... И всё нормально работает... включая комментарии! И парсеры уже есть! (в браузере даже есть готовый класс DOMParser)

var code = `
<my-code>
	<tag1 name1="value1" name2="value2" />
	<tag1 name1="value1" name2="value2" />
</my-code>
`;

var parser = new DOMParser();
var doc = parser.parseFromString(code, "text/xml");

// дальше работа с иерархией разобранных элементов
console.log(doc);

Tachyon 21.06.2019 09:11

Цитата:

Сообщение от Malleys (Сообщение 509272)
Почему бы не использовать XML?

Потому что приходится работать с тем, что генерирует драйвер устройства. Программиста, написавшего драйвер, по ряду причин достать и заставить переписать STDOUT на XML или JSON намного сложнее.

Цитата:

Сообщение от Alexandroppolus (Сообщение 509271)
Немного пофиксил, на этот раз вроде всё как надо :)

Спасибо, на такой вариант, с парсингом параметров непосредственно при поиске тегов, несколько затрудняет последующую обработку нестандартных параметров (которые без =значение). Сейчас у меня получается делать это последовательно.

Конкретный пример:
<value name='name<">1' value="value1">
<value name='name2' value = "еще <одно> значение">
<block name="name3" property="tagret" clear>
   "текст"
   Этот тег не обрабатывается и остается в тексте:
   <левый тег парсить="не нужно">
   А вот этот тег нужно вырезать из содержимого
   и обработать с другими аналогичными:
   <value name='name3' clear value="value3">
</block>
<text name="text1" property="target">
   а это незакрытый тег, содержимое берется до следующего
   такого же или до конца. Левый тег остается в тексте.
   <левый тег парсить="не нужно">
<text clear name="text2" property="target">
   второй текстовый блок, читается до конца.
   <block name="inner block" property="tagret">
      А этот текстовый блок тоже вырезается из содержимого
      и парсится отдельно.
   </block>


Получается как-то так. Не сомневаюсь, что решение далеко от оптимального, но вроде бы работает. По крайней мере пока, с теми примерами вывода, которые я смог получить.
var string = xmlHttp.responseText.replace(/[\r\n]/g, '');
var value = new Array(), block = new Array(), text = new Array();

string = string.replace(/<\s*value(\s+(?:"[^"]*"|'[^']*'|[^>"'])*)?>/gi, function(a,b) {
   var obj = PropParse(b);
   value.push(obj);
   return '';
});

var reg = /<\s*block(\s+(?:"[^"]*"|'[^']*'|[^>"'])*)?>/i;
while (reg.test(string)) {
   var pos1 = string.match(reg);
   var pos2 = string.match(/<\s*\/\s*block\s*>/i);
   var obj = PropParse(pos1[1]);
   obj.innerValue = string.substring(pos1.index+pos1[0].length, pos2.index);
   string = string.substr(0, pos1.index) + string.substr(pos2.index+pos2[0].length);
   block.push(obj);
}

var res = string.split(/<\s*text\s+/i);
for (var i = 0; i < res.length; i++) {
   res[i].replace(/^\s*((?:"[^"]*"|'[^']*'|[^>"'])*)>(.*)$/, function(a,b,c) {
      var obj = PropParse(b);
      obj.innerValue = c;
      if (!obj.clear) obj.clear = false;
      text.push(obj);
   });
}

function PropParse(str) {
   var obj = new Object();
   var val = str.replace(/\s*([\w-]+)\s*[=:]\s*("[^"]*"|'[^']*'|[\w-]+)\s*/g, function(a,b,c) {
      obj[b.trim()] = c.trim().replace(/(^["']|["']$)/g, '');
      return '';
   });
   val = val.replace(/[^\s\w-]/, '');
   var left = val.split(/\s/);
   for (var i = 0; i < left.length; i++) {
      obj[left[i].trim()] = true;
   }
   return obj;
}

Alexandroppolus 21.06.2019 13:04

вот примерно так (см. в консоль)

https://jsfiddle.net/n3a1ofb0/

здесь есть всё, кроме экранирования кавычек в атрибутах, о чем были регулярки с \\\\ на предыдущей странице. Если надо, можно это добавить

Tachyon 21.06.2019 13:43

Большое спасибо! Этот вариант выглядит намного интереснее. Прогоню его на имеющихся у меня тестовых вариантах, если возникнут вопросы - напишу.

Последний вопрос: задача в чем-то противоположная. Произвольно внутри текстовых блоков могут встречаться теги типа
< format value : 'value<">' name="name">
   text<"'>text<break>text< break >text
< / format>

Вырезать их из текста не нужно, парсить их параметры не нужно. Нужно только заменить имеющиеся между открывающим и закрывающим тегами команды <break> на '\n'. И сделать это только для тегов <format>.

Alexandroppolus 22.06.2019 16:41

Цитата:

Сообщение от Tachyon
внутри текстовых блоков

Текстовый блок - это <block> или <text>? :)

Tachyon 25.06.2019 09:02

Цитата:

Сообщение от Alexandroppolus (Сообщение 509332)
Текстовый блок - это <block> или <text>? :)

Видел в <text>, но, теоретически, может встретиться и в <block>.

В <block> точно встречается другой тег с такой же задачей (замена <break> на \n), выглядит немного иначе, но, если найти решение для <text>, думаю, я смогу адаптировать его и для второго.

Второй тег, встречающийся только в <block> выглядит так:
message("text<break>text")
Кавычки одинарные или двойные, пробелы произвольные. Думаю, здесь вполне подойдет подсказанное ранее решение - выборка текста до первого не заключенного в кавычки символа '>', только, соответственно, вместо кавычек скобка. А вот замена приходит в голову только через substr.

Alexandroppolus 26.06.2019 10:00

Цитата:

Сообщение от Tachyon
Второй тег, встречающийся только в <block> выглядит так:
message("text<break>text")

Не понял. Т.е. тег на самом деле выглядит как вызов функции? Можно пример?
Цитата:

Сообщение от Tachyon
В <block> точно встречается другой тег с такой же задачей (замена <break> на \n), выглядит немного иначе, но, если найти решение для <text>, думаю, я смогу адаптировать его и для второго

Тоже давай пример

Tachyon 26.06.2019 15:21

Ну так это и был пример. Это действительно выглядит как вызов функции, и в результате должно будет выводиться либо в alert, либо в console.log (но это уже совсем другая задача, с обработкой распарсенного в объекты текста, и ее я вполне себе решу самостоятельно). Мне очень понравилось предложенное Вами решение, в отличие от моего - должно работать значительно шустрее (насколько я понимаю). Если каким-то таким же образом получится решить и эту задачу - будет совсем прекрасно.

Я уже не буду утомлять уточнениями (например, в итоговом массиве объектов некоторые теги типа <text> и <block> должны объединяться, исходя из их свойств. Но это я тоже попробую разобраться в вашем примере и доделать самому.

А выглядеть в исходном тексте будет как-то так:
<value name='name<">1' value="value1">
<value name='name2' value = "еще <одно> значение">
<block name="name3" property="tagret" clear>
   "текст"
   <левый тег парсить="не нужно">
   message ('text<">text<break>text' )
   <value name='name3' clear value="value3">
</block>
<text name="text1" property="target">
   <format name='form1'>
      text<">text<break>text< break >text
   </format>
   <левый тег парсить="не нужно">
   <value name='inner' clear value="inner">
<text clear name="text2" property="target">
   второй текстовый блок, читается до конца.
   <block name="inner block" property="tagret">
      text text text
      <format name='form1'>
         text<">text<break>text< break >text
      </format>
      text  text text
      message("text< break>text")
      <value name='inner' clear value="inner">
   </block>

Alexandroppolus 26.06.2019 17:17

Цитата:

Сообщение от Tachyon
Ну так это и был пример. Это действительно выглядит как вызов функции, и в результате должно будет выводиться либо в alert, либо в console.log (но это уже совсем другая задача, с обработкой распарсенного в объекты текста

В общем случае это может быть совсем не другая задача. Например, если вдруг встретится message("aaa<value ...>bbb"), то, наверно, <value> парсить не надо, как если бы он просто был в тексте?
Тот же вопрос про случай <aaaa att="<value a=b>" > - тут у нас тег value попал в атрибут левого тега, и надо ли этот value парсить как обычный.

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

Если будет время, могу наброски сделать. Но хорошо бы все кейсы описать.

Tachyon 26.06.2019 17:46

Цитата:

Сообщение от Alexandroppolus
Например, если вдруг встретится message("aaa<value ...>bbb"), то, наверно, <value> парсить не надо, как если бы он просто был в тексте?

Да, у message только текст. Но, теоретически, такого сочетания встречаться не должно. Если встретится - то это баг.
Цитата:

Сообщение от Alexandroppolus
Тот же вопрос про случай <aaaa att="<value a=b>" >

То же самое. Управляющих тегов в свойствах другого тега быть не должно. Только в теле между открывающим и закрывающим тегами. Или в корне. Точно так же не должно быть вложенных друг в друга блоков <block> или <format> (или любых других одинаковых тегов кроме <text>, с ним отдельная песня).


Часовой пояс GMT +3, время: 09:41.