Регулярное выражение с игнорированием символа в кавычках
Здравствуйте. Прошу помочь мне в составлении регулярного выражения (или сообщить, если решение этой задачи невозможно в рамках регулярных выражений JS).
Задача: нужно распарсить код, по структуре похожий на HTML. Пример: <tag1 name1="value1" name2 = 'value2'> < tag1 name1="value1"name2 = 'value2' > Наличие и расположение пробелов может быть произвольным. Кавычки могут быть одинарными или двойными. Требуется: выбрать весь тег, начиная с открывающего символа < и заканчивая закрывающим > включительно. Задача осложняется тем, что значения, заключенные в кавычки, могут содержать символы < и >. Следовательно, выражение типа /<\s*tag1([^>]*)>/gi Не подходит. Можно ли как-нибудь выбрать текст до первого не заключенного в кавычки (одинарные или двойные) символа > при помощи регулярного выражения, или нужно писать парсер с логическим анализом? |
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)) |
Подправил маленько
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)) |
Представленные варианты ошибочно срабатывают на
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)); |
Большое спасибо! Немного усложнил последний вариант (на случай отсутствия параметров в теге) и погонял его с разными комбинациями, которые смог придумать, вроде бы везде сработал корректно:
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*>/ регулярным выражением не сделать? |
Цитата:
Код:
<tag1""""""""""""""""""/> Цитата:
Код:
<tag1""""""""""""""""""/> Цитата:
/<\s*tag1(?:\s*[^\s=>]+\s*=\s*(?:'(?:\\\\|\\'|[^\\'])*'|"(?:\\\\|\\"|[^\\"])*"))*\s*>/giКак оно выглядит? |
Цитата:
Чтобы парсить полученную строку с параметрами я составил вот такой regexp, вроде бы делает именно то, что надо: /\s*([\w-]+)\s*[=:]\s*("[^"]*"|'[^']*'|[\w-]+)\s*/g Единственный недостаток - могут встречаться ключи без значения. Типа <tag1 name='value' clear> Подразумевается, что clear=true. Но ретроспективные и опережающие проверки на IE 11 и FF 56, видимо, совсем не работают. Так что я сделаю replace по предыдущему выражению, чтобы удалило из строки все найденные пары ключ=значение, а что останется - буду уже парсить отдельно. |
Цитата:
Цитата:
/<\s*tag1(?:\s*(?:[\w-]+)\s*(?:[:=]\s*(?:'(?:\\\\|\\'|[^\\'])*'|"(?:\\\\|\\"|[^\\"])*"|[\w-]+))?)*\s*>/giКак это выглядит? |
Цитата:
Цитата:
Немного пофиксил, на этот раз вроде всё как надо :) /<\s*tag1(?:\s+(?:[\w-]+(?![\w-])(?:\s*[:=]\s*(?:'(?:\\\\|\\'|[^\\'])*'|"(?:\\\\|\\"|[^\\"])*"|[\w-]+))?\s*)+)?\s*>/gi |
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); |
Цитата:
Цитата:
Конкретный пример: <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; } |
вот примерно так (см. в консоль)
https://jsfiddle.net/n3a1ofb0/ здесь есть всё, кроме экранирования кавычек в атрибутах, о чем были регулярки с \\\\ на предыдущей странице. Если надо, можно это добавить |
Большое спасибо! Этот вариант выглядит намного интереснее. Прогоню его на имеющихся у меня тестовых вариантах, если возникнут вопросы - напишу.
Последний вопрос: задача в чем-то противоположная. Произвольно внутри текстовых блоков могут встречаться теги типа < format value : 'value<">' name="name"> text<"'>text<break>text< break >text < / format> Вырезать их из текста не нужно, парсить их параметры не нужно. Нужно только заменить имеющиеся между открывающим и закрывающим тегами команды <break> на '\n'. И сделать это только для тегов <format>. |
Цитата:
|
Цитата:
В <block> точно встречается другой тег с такой же задачей (замена <break> на \n), выглядит немного иначе, но, если найти решение для <text>, думаю, я смогу адаптировать его и для второго. Второй тег, встречающийся только в <block> выглядит так: message("text<break>text") Кавычки одинарные или двойные, пробелы произвольные. Думаю, здесь вполне подойдет подсказанное ранее решение - выборка текста до первого не заключенного в кавычки символа '>', только, соответственно, вместо кавычек скобка. А вот замена приходит в голову только через substr. |
Цитата:
Цитата:
|
Ну так это и был пример. Это действительно выглядит как вызов функции, и в результате должно будет выводиться либо в 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> |
Цитата:
Тот же вопрос про случай <aaaa att="<value a=b>" > - тут у нас тег value попал в атрибут левого тега, и надо ли этот value парсить как обычный. В общем, скорее всего, мой подход надо расширить на все активные элементы в тексте, обходить текст один раз, тогда ни на чем не споткнемся. Если будет время, могу наброски сделать. Но хорошо бы все кейсы описать. |
Цитата:
Цитата:
|
Часовой пояс GMT +3, время: 09:41. |