/ / Regex для видалення дублікатів скриптів з html-файлу - regex

Regex для видалення дублікатів скриптів з html-файла - regex

Скажімо, у вас є HTML-файл з пароюкопії сценаріїв, тобто декілька зовнішніх тегів скриптів для одного ресурсу, наприклад завантаження jquery 3 рази на сторінку. Чи є ефективний регулярний вираз, який може видалити дублікати, але зберегти перший на місці. Дублікати будуть всі з тим же ім'ям src.

Мова - PHP, і ось хороший приклад:

До:

<script src="js/jquery.js" type="text/javascript"></script>
some content
<script src="js/jquery.js" type="text/javascript"></script>
more content
<script src="js/jquery.js" type="text/javascript"></script>

Після:

<script src="js/jquery.js" type="text/javascript"></script>
some content
more content

Відповіді:

2 для відповіді № 1

Відмова від відповідальності:

Багато хто з правом заявить, що використання регулярних виразів для розбору нерегулярних мов, таких як HTML, загрожує небезпекою. І вони вірні. Єдиний шлях до надійно розбір цих мов здійснюється за допомогою аналізатораспеціально розроблений для виконання завдання. Рішення, що використовує регулярні вирази, як правило, має багато особливих випадків тематичного тексту, що призведе до його відмови, що призведе до помилкових позитивних результатів та відсутніх збігів.

Це сказало ...

Якщо хтось наполягає на використанні регулярних виразів дообробляти розмітку HTML / XML, і вони знають про властиві їм обмеження, є способи розробити рішення регулярного виразів, яке може мінімізувати ці потенційні підводні камені та зробити "досить добре" робота (залежно від конкретних вимог питання). Однак для правильної обробки багатьох рідкісних (але дійсних та можливих) крайових випадків (наприклад, правильної обробки атрибутів тегів HTML, що містять <> кутові дужки, наприклад), правильний регулярний вираз часто може бути досить складним і не для слабких.

Розуміння наступного регекс-рішеннявимагає досить глибокого розуміння мови регулярних виразів та основної механіки двигуна регулярних виразів. Безумовно, є приклади тексту розмітки, які спричинить його збій, але наступне рішення повинно зробити досить хорошу роботу для багатьох випадків типової розмітки.

Ось перевірена функція PHP, яка видаляє SCRIPT елементи, що мають дублікат SRC значення атрибутів:

// Strip all SCRIPT elements having duplicate SRC URLs.
function stripDuplicateScripts($text) {
$re = "%
# Match duplicate SCRIPT element having same SRC attribute URL.
(                   # $1: Everything up to duplicate SCRIPT element.
<script           # literal start of script open tag
(?:               # Zero or more attributes before SRC.
s+             # Whitespace required before attribute.
(?!srcb)       # Assert this attribute is not "SRC".
[w-.:]+       # Non-SRC attribute name.
(?:             # Attribute value is optional.
s*=s*       # Value separated by =, optional ws.
(?:           # Group attribute value alternatives.
"[^"]*"     # Either a double quoted value,
| "[^"]*"  # or a single quoted value,
| [w-.:]+   # or an unquoted value.
)             # End group of value alternatives.
)?              # Attribute value is optional.
)*                # Zero or more attributes before SRC.
s+               # Whitespace required before SRC attrib.
src               # Required SRC attribute name.
s*=s*           # Value separated by =, optional ws.
([""])           # $2: Attrib value opening quote.
((?:(?!2).)+)    # $3: SRC attribute value (a URL).
2                # Attrib value closing quote.
(?:               # Zero or more attributes after SRC.
s+             # Whitespace required before attribute.
[w-.:]+       # Attribute name.
(?:             # Attribute value is optional.
s*=s*       # Value separated by =, optional ws.
(?:           # Group attribute value alternatives.
"[^"]*"     # Either a double quoted value,
| "[^"]*"  # or a single quoted value,
| [w-.:]+   # or an unquoted value.
)             # End group of value alternatives.
)?              # Attribute value is optional.
)*                # Zero or more attributes after SRC.
s*               # Optional whitespace before tag close.
>                 # End of SCRIPT open tag.
</scripts*>      # SCRIPT close tag.
.*?               # Stuff up to duplicate script element.
)                   # End $1: Everything up to duplicate SCRIPT.
<script             # literal start of script open tag
(?:                 # Zero or more attributes before SRC.
s+               # Whitespace required before attribute.
(?!srcb)         # Assert this attribute is not "SRC".
[w-.:]+         # Non-SRC attribute name.
(?:               # Attribute value is optional.
s*=s*         # Value separated by =, optional ws.
(?:             # Group attribute value alternatives.
"[^"]*"       # Either a double quoted value,
| "[^"]*"    # or a single quoted value,
| [w-.:]+     # or an unquoted value.
)               # End group of value alternatives.
)?                # Attribute value is optional.
)*                  # Zero or more attributes before SRC.
s+                 # Whitespace required before SRC attrib.
src                 # Required SRC attribute name.
s*=s*             # Value separated by =, optional ws.
([""])             # $4: Attrib value opening quote.
3                  # This script must have duplicate SRC URL.
4                  # Attrib value closing quote.
(?:                 # Zero or more attributes after SRC.
s+               # Whitespace required before attribute.
[w-.:]+         # Attribute name.
(?:               # Attribute value is optional.
s*=s*         # Value separated by =, optional ws.
(?:             # Group attribute value alternatives.
"[^"]*"       # Either a double quoted value,
| "[^"]*"    # or a single quoted value,
| [w-.:]+     # or an unquoted value.
)               # End group of value alternatives.
)?                # Attribute value is optional.
)*                  # Zero or more attributes after SRC.
s*                 # Optional whitespace before tag close.
>                   # End of SCRIPT open tag.
</scripts*>        # SCRIPT close tag.
s*                 # Strip whitespace following duplicate.
%six";
while (preg_match($re, $text)) {
$text = preg_replace($re, "$1", $text);
}
return $text;
}

У наведеній вище функції використовується один регулярний вираз, який єзастосовується рекурсивно, поки не знайдено збігів. Хоча на перший погляд регулярний вигляд схожий на монстра, він насправді досить прямолінійний (якщо ви добре розбираєтесь у синтаксисі регулярних виразів) і більшість тексту складається з описових коментарів. Складність цього регулярного виразу необхідна для обробки різноманітних форматів атрибутів / значень, дозволених HTML. Наприклад, SCRIPT Теги можуть мати будь-яку кількість атрибутів до і після SRC атрибут The SRC значення атрибута може бути одиничним або подвійним. Усі інші атрибути можуть мати значення, які цитуються або цитуються, і можуть взагалі не мати значення. Цитовані атрибути можуть містити <> кутові дужки.


1 для відповіді № 2

Проста відповідь на ваше запитання "Чи є ефективний регулярний вираз, який може видалити дублікати, але зберегти перше на місці": AFAIK, ні - немає ефективного регулярного вираження для цього.

Основний вираз (який може бути ДУЖЕ неефективним, залежно від вихідного тексту):

(<scripts+type="text/javascript"s+src="[^"]*">s*</script>)([sS]*?)1

замінити:

$1$2

Це не справляється із сильним відхиленням будь-якого тегу (що в цьому випадку повинні бути ідентичними один одному) із стандартної форми:

<script type="text/javascript" src="javascript.js"></script>

Він по суті повинен збігатися і видаляти другийекземпляр тегу сценарію - який відповідає ТОЧНО попередньому тегу сценарію. Якщо вам потрібна більша гнучкість у точному форматі тегу сценарію, і ви знаєте лише, що ім'я файлу (URL) буде однаковим, ви можете використовувати цей вираз:

(<scripts+type="text/javascript"s+src="([^"]*)"></script>)([sS]*?)<scripts+type="text/javascript"s+src="2"></script>

заміни

$1$3

що дозволить вирішити відмінності у пробілі, але може бути навіть менш ефективним (майже вдвічі), залежно від вихідного HTML.

На ефективність впливає кількість тексту між двома примірниками тегів (приблизно втричі більше обробки на тег, що не зовсім відповідає, ніж для тегів, які відповідають)

РЕДАГУВАТИ Я вірю, що доведеться запускати один раз для кожногодублікат (три випадки тегу скрипту потребують двох запусків цієї заміни, щоб зменшити його до одного випадку), хоча я "не в змозі повністю перевірити PHP в даний момент.


0 для відповіді № 3

Якщо ви використовуєте регулярний гекс для розбору html doesen, то не заважайте, щось просте
може спрацювати:

$samp = "
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jOOPquery.js" type="text/javascript"></script>
some content
<script type="text/javascript" src="js/jOOPquery.js"></script>
<script src="js/jquery.js" type="text/javascript"></script>
more content
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jOOPquery.js" type="text/javascript"></script>
";

$regex =
"(?xs)
(<script (?=s)[^>]* (?i:(?<=s)srcs*=s* "s*([^"]*?)s*") [^>]* (?<!/)>s*</scripts*>
.*?
)<script (?=s)[^>]* (?i:(?<=s)srcs*=s* "s*2s*") [^>]* (?<!/)>s*</scripts*>s*
";

while ($samp =~ s/$regex/$1/g) {}


print "$sampn";

Вихід:

<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jOOPquery.js" type="text/javascript"></script>
some content
more content