С помощью DOMDocument()
, Я заменяю ссылки в $message
и добавив некоторые вещи, такие как [@MERGEID]
, Когда я сохраняю изменения с $dom_document->saveHTML()
, ссылки получают «своего рода» в кодировке URL. [@MERGEID]
становится %5B@MERGEID%5D
,
Позже в моем коде мне нужно заменить [@MERGEID]
с удостоверением личности. Поэтому я ищу urlencode('[@MERGEID]')
— тем не мение, urlencode()
изменяет рекламу в символе (@) на% 40, а saveHTML () оставляет его в покое. Так что нет совпадений — '%5B@MERGEID%5D' != '%5B%40MERGEID%5D'
Теперь я знаю, может бежать str_replace('%40', '@', urlencode('[@MERGEID]'))
чтобы получить то, что мне нужно, чтобы найти переменную слияния в $ message.
Мой вопрос, Какие спецификации RFC использует DOMDocument и почему они отличаются от urlencode или даже rawurlencode? Могу ли я что-нибудь сделать, чтобы сохранить str_replace?
Демо-код:
$message = '<a href="http://www.google.com?ref=abc" data-tag="thebottomlink">Google</a>';
$dom_document = new \DOMDocument();
libxml_use_internal_errors(true); //Supress content errors
$dom_document->loadHTML(mb_convert_encoding($message, 'HTML-ENTITIES', 'UTF-8'));
$elements = $dom_document->getElementsByTagName('a');
foreach($elements as $element) {
$link = $element->getAttribute('href'); //http://www.google.com?ref=abc
$tag = $element->getAttribute('data-tag'); //thebottomlink
if ($link) {
$newlink = 'http://www.example.com/click/[@MERGEID]?url=' . $link;
if ($tag) {
$newlink .= '&tag=' . $tag;
}
$element->setAttribute('href', $newlink);
}
}
$message = $dom_document->saveHTML();
$urlencodedmerge = urlencode('[@MERGEID]');
die($message . ' and url encoded version: ' . $urlencodedmerge);
//<a data-tag="thebottomlink" href="http://www.example.com/click/%5B@MERGEID%5D?url=http://www.google.com?ref=abc&tag=thebottomlink">Google</a> and url encoded version: %5B%40MERGEID%5D
Я считаю, что эти две кодировки служат разным целям. urlencode()
кодирует «строка, которая будет использоваться в части запроса URL «, в то время как $element->setAttribute('href', $newlink);
кодирует полный URL-адрес для использования в качестве URL-адреса.
Например:
urlencode('http://www.google.com'); // -> http%3A%2F%2Fwww.google.com
Это удобно для кодирования часть запроса, но это не может быть использовано на <a href='...'>
,
Тем не мение:
$element->setAttribute('href', $newlink); // -> http://www.google.com
будет правильно кодировать строку, чтобы она все еще могла использоваться в href
, Причина, по которой он не может кодировать @
потому что он не может сказать, @
является частью запроса или частью userinfo
или же email
URL (например: mailto:[email protected]
или же [email protected]
)
Вместо того, чтобы использовать [@MERGEID]
, ты можешь использовать @@MERGEID@@
, Затем вы замените его своим идентификатором позже. Это решение не требует от вас даже использования urlencode
,
Если вы настаиваете на использовании urlencode
, вы можете просто использовать% 40 вместо @. Итак, ваш код будет таким $newlink = 'http://www.example.com/click/[%40MERGEID]?url=' . $link;
Вы также можете сделать что-то вроде $newlink = 'http://www.example.com/click/' . urlencode('[@MERGEID]') . '?url=' . $link;
urlencode
функция и rawurlencode
в основном основаны на RFC 1738
, Однако с 2005 года текущий RFC, используемый для стандарта URI, является RFC 3986
,
С другой стороны, расширение DOM использует кодировку UTF-8, которая основана на RFC 3629 . Используйте utf8_encode () и utf8_decode () для работы с текстами в кодировке ISO-8859-1 или Iconv для других кодировок.
Общий синтаксис URI предписывает новые схемы URI, которые обеспечивают
представление символьных данных в URI должно, по сути,
представляют символы из незарезервированного набора без перевода, и
следует преобразовать все остальные символы в байты в соответствии с UTF-8, и
затем кодировать эти значения в процентах.
Вот функция для декодирования URL в соответствии с RFC 3986
,
<?php
function myUrlEncode($string) {
$entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%2B', '%24', '%2C', '%2F', '%3F', '%25', '%23', '%5B', '%5D');
$replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "+", "$", ",", "/", "?", "%", "#", "[", "]");
return str_replace($entities, $replacements, urldecode($string));
}
?>
Обновить:
Поскольку UTF8 был использован для кодирования $message
:
$dom_document->loadHTML(mb_convert_encoding($message, 'HTML-ENTITIES', 'UTF-8'))
использование urldecode($message)
при возврате URL без процентов.
die(urldecode($message) . ' and url encoded version: ' . $urlencodedmerge);
Коренная причина вашей проблемы было очень хорошо объяснено с технической точки зрения.
Однако, на мой взгляд, в вашем подходе есть концептуальный недостаток, и это созданный ситуация, которую вы сейчас пытаетесь исправить.
Обрабатывая ваш вход $message
через объект DomDocument вы перешли на более высокий уровень абстракции. Неправильно манипулировать как уникальной простой строкой чем-то, что было «продвинуто» в поток HTML.
Вместо того, чтобы пытаться воспроизвести поведение DomDocument, используйте саму библиотеку, чтобы найти, извлечь и заменить значения, представляющие интерес:
$token = 'blah blah [@MERGEID]';
$message = '<a id="' . $token . '" href="' . $token . '"></a>';
$dom = new DOMDocument();
$dom->loadHTML($message);
echo $dom->saveHTML(); // now we have an abstract HTML document
// extract a raw value
$rawstring = $dom->getElementsByTagName('a')->item(0)->getAttribute('href');
// do the low-level fiddling
$newstring = str_replace($token, 'replaced', $rawstring);
// push the new value back into the abstract black box.
$dom->getElementsByTagName('a')->item(0)->setAttribute('href', $newstring);
// less code written, but works all the time
$rawstring = $dom->getElementsByTagName('a')->item(0)->getAttribute('id');
$newstring = str_replace($token, 'replaced', $rawstring);
$dom->getElementsByTagName('a')->item(0)->setAttribute('id', $newstring);
echo $dom->saveHTML();
Как показано выше, сегодня мы пытаемся решить проблему, когда ваш токен находится внутри href, но однажды мы можем захотеть найти и заменить тег в другом месте документа. Чтобы учесть этот случай, не беспокойтесь о том, чтобы ваш низкоуровневый код учитывал HTML.
(альтернативным вариантом будет не загружать DomDocument, пока не будут выполнены все низкоуровневые замены, но я предполагаю, что это не практично)
Полное доказательство концепции:
function searchAndReplace(DOMNode $node, $search, $replace) {
if($node->hasAttributes()) {
foreach ($node->attributes as $attribute) {
$input = $attribute->nodeValue;
$output = str_replace($search, $replace, $input);
$attribute->nodeValue = $output;
}
}
if(!$node instanceof DOMElement) { // this test needs double-checking
$input = $node->nodeValue;
$output = str_replace($search, $replace, $input);
$node->nodeValue = $output;
}
if($node->hasChildNodes()) {
foreach ($node->childNodes as $child) {
searchAndReplace($child, $search, $replace);
}
}
}
$token = '<>&;[@MERGEID]';
$message = '<a/>';
$dom = new DOMDocument();
$dom->loadHTML($message);
$dom->getElementsByTagName('a')->item(0)->setAttribute('id', "foo$token");
$dom->getElementsByTagName('a')->item(0)->setAttribute('href', "http://foo@$token");
$textNode = new DOMText("foo$token");
$dom->getElementsByTagName('a')->item(0)->appendchild($textNode);
echo $dom->saveHTML();
searchAndReplace($dom, $token, '*replaced*');
echo $dom->saveHTML();
Если вы используете saveXML()
это не испортит кодировку saveHTML()
делает:
PHP
//your code...
$message = $dom_document->saveXML();
РЕДАКТИРОВАТЬ: также удалите тег XML:
//this will add an xml tag, so just remove it
$message=preg_replace("/\<\?xml(.*?)\?\>/","",$message);
echo $message;
Выход
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><a href="http://www.example.com/click/[@MERGEID]?url=http://www.google.com?ref=abc&tag=thebottomlink" data-tag="thebottomlink">Google</a></body></html>
Обратите внимание, что оба по-прежнему правильно конвертировать &
в &
Разве не имеет смысла просто урленкодировать оригинал [@mergeid], кроме того, чтобы сохранить его в первую очередь? Ваш поиск должен соответствовать без str_replace?
$newlink = 'http://www.example.com/click/'.urlencode('[@MERGEID]').'?url=' . $link;
Я знаю, что это не отвечает на первый пост вопроса, но вы не можете размещать код в комментариях, насколько я могу судить.