Если говорить безотносительно Drupal, то для обработки HTML в PHP стоит использовать встроенный класс DOMDocument. Стоит, как минимум, потому, что через него (через метод loadHTML) можно пропустить HTML код с целью исправления в нём ошибок HTML форматирования (незакрытые теги и т.п.). Касательно Вашей задачи, с помощью него можно получить доступ именно к текстовым узлам дерева HTML.
Пример парсинга (без корректного алгоритма по замене кавычек):
<?php
$html = '"текст в кавычках" текст "ещё текст в кавычках" <a href="http://ya.ru"><em>неправильный порядок вложенности элементов</a></em> текст <div>незакрытый элемент';
$domDocument = loadHtml($html);
$xpath = new DOMXpath($domDocument);
// Выбирает только текстовые узлы
foreach ($xpath->query('/html/body//text()') as $textNode)
{
// Тут можно производить замены кавычек, учитывая текст из предыдущих $textNode
$textNode->data = str_replace('"', '«', $textNode->data);
}
print htmlspecialchars(saveHtml($domDocument));
/*
Выведет:
«текст в кавычках« текст «ещё текст в кавычках« <a href="http://ya.ru"><em>неправильный порядок вложенности элементов</em></a> текст <div>незакрытый элемент</div>
*/
Функции, используемые в вышеприведённом коде (содержат различные исправления для соответствующих методов класса DOMDocument):
<?php
/**
* @return DOMDocument
*/
function loadHtml($html, $charset = 'utf-8')
{
$domDocument = new DOMDocument();
// Т.к. функция DOMDocument::loadHTML конвертирует сущности " " в обычные пробелы,
// приходится экранировать эти сущности. Засипи вида "&nbsp;" экранируем для того,
// чтобы при обратном преобразовании они по ошибке не превратились в " "
$html = str_replace('&nbsp;', '&amp;nbsp;', $html);
$html = str_replace(' ', '&nbsp;', $html);
// Удаляем символы "\r", т.к. DOMDocument::loadHTML() преобразует их в " "
$html = str_replace("\r", '', $html);
$html = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=' . $charset . '"/>
<title></title>
</head>
<body>' . $html . '</body>
</html>
';
// Функция DOMDocument::loadHTML может генерировать сообщения об ошибках, которые нам
// не нужны (например, о незакрытом теге), т.к. мы используем данную функцию для
// коррекции HTML кода. Оператор @ ошибки у данной функции не подавляет.
$useErrorsOld = libxml_use_internal_errors(true);
$domDocument->loadHTML($html);
libxml_use_internal_errors($useErrorsOld);
return $domDocument;
}
/**
* @return string
*/
function saveHtml(DOMDocument $domDocument)
{
// Экспортируем в формате XHTML
$html = $domDocument->saveXml();
$html = str_replace('&nbsp;', ' ', $html);
$html = str_replace('&amp;nbsp;', '&nbsp;', $html);
$html = preg_replace('/^\s*\<\?xml\s*[^\>]*\>\s*/is', '', $html);
// Удаляем <![CDATA[]]>, которым оборачивается содержимое тега <script></script> при
// экспорте через DOMDocument::saveXml
$html = preg_replace('/(\<script(\s*[^\>]*)?\>)\<\!\[CDATA\[/is', '$1', $html);
$html = preg_replace('/\]\]\>(\<\/script\>)/is', '$1', $html);
return preg_replace('/^.*?<body>(.*?)<\/body>\s*<\/html>$/is', '$1', $html);
}
P.S.: на хабре HTML сущности в теге <source>...</source> не экранируются для вывода кода в неизменном виде (жесть какая-то), я произвёл экранирование вручную, поэтому учтите, что код может стать некорректным, если программисты хабра исправят этот баг без должной замены в существующих сообщениях.