Это не идеально (для этого нужны парсеры HTML), но оно будет работать для подавляющего большинства файлов HTML:
(^|>)[^<>]*##[^#]*##[^<>]*(<|$)
Идея проста. Вы ищете строку, которая находится за пределами тегов. Чтобы быть вне тегов, ближайшая предшествующая угловая скобка должна закрываться (или вообще нет скобки), а ближайшая следующая должна открываться (или не иметь). Это предполагает, что угловые скобки не используются в значениях атрибутов.
Если вы действительно заботитесь о том, чтобы имя атрибута было «значением», то вы можете сопоставить:
value\s*=\s*"([^\"]|\\\")*##[^#]*##([^\"]|\\\")*\"
... а затем просто отмените совпадение (!preg_match(...)
).