-1

I have a html-string that contains multiple parts like this:

$source = '
    <span id="pass_AwfpSYYUsn" data-id="AwfpSYYUsn" class="pointer unlockFieldChild" data-client="51" data-status="closed"><i class="fa fa-lock"></i> Show</span>
    <!-- OTHER HTML STUFF -->
    <span id="pass_DbTD7TjEDX" data-id="DbTD7TjEDX" class="pointer unlockFieldChild" data-client="51" data-status="closed"><i class="fa fa-lock"></i> Show</span>
  ';

I would like to replace all of those with this:

[pass id="AwfpSYYUsn"]PASSWORD OR EMPTY[/pass]
<!-- OTHER HTML STUFF -->
[pass id="DbTD7TjEDX"]PASSWORD OR EMPTY[/pass]

The data-client and the data-id is what I need for this

What I did

preg_match_all('@<span id="pass_(.*?)".*?data-client="(.*?)".*?</span>@', $content, $matches, PREG_SET_ORDER);
        foreach ($matches as $match) {

            $pw = '';
            /* This checks and fills the password, not really relvant to the question */
            if ($aG->getCanPassword()) {
                $p = $em->getRepository('AppBundle:PasswordList')->findOneBy(array(
                    'code' => $match[1],
                ));
                if ($p !== null) {
                    $pw = $p->getPass();
                }
            }

            $content = str_replace('<span id="pass_' . $match[1] . '" data-id="' . $match[1] . '" class="pointer unlockFieldChild" data-client="' . $match[2] . '" data-status="closed"><i class="fa fa-lock"></i> Show</span>', '[pass id="' . $match[1] . '"]' . $pw . '[/pass]', $content);

        }

This works, but I don't really like the str_replace approach, is there a way to do it in a (single) preg_replace, possibly w/out the foreach?

Any hint appreciated!

PrimuS
  • 2,505
  • 6
  • 33
  • 66
  • 1
    use XPath and/or DomDocument instead of regular expression – tereško Oct 18 '17 at 10:06
  • 1
    *"Any hint appreciated!*" ... [hint - the pon̷y he comes](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454) – CD001 Oct 18 '17 at 10:09

2 Answers2

0

What exactly is the problem with preg_replace? Use $1, $2... in the replace string to insert the captured stuff:

preg_replace('%<span id="pass_(.*?)".*?data-client="(.*?)".*?</span>%', '[pass id="$1"]PASSWORD OR EMPTY[/pass]', $subject);
Matschek
  • 205
  • 1
  • 9
0

Regex is not the way to edit html content. html is too complicated and full of traps to obtain a trusted result. PHP has already a build-in html parser based on libxml:

$content = '
    <span id="pass_AwfpSYYUsn" data-id="AwfpSYYUsn" class="pointer unlockFieldChild" data-client="51" data-status="closed"><i class="fa fa-lock"></i> Show</span>
    <!-- OTHER HTML STUFF -->
    <span id="pass_DbTD7TjEDX" data-id="DbTD7TjEDX" class="pointer unlockFieldChild" data-client="51" data-status="closed"><i class="fa fa-lock"></i> Show</span>
  ';

$dom = new DOMDocument;
$state = libxml_use_internal_errors(true);
$dom->loadHTML('<div id="root">' . $content . '</div>'); // add a fake root element
// (usefull if you don't work with a full html document)
$xp = new DOMXPath($dom);
$nodeList = $xp->query('//span[starts-with(@id, "pass_")]');

foreach ($nodeList as $node) {
    $idpass = explode('_', $node->getAttribute('id'), 2)[1];

    $pw = '';
    if ($aG->getCanPassword()) {
        $p = $em->getRepository('AppBundle:PasswordList')->findOneBy(['code' => $idpass]);
        if ($p !== null) {
            $pw = $p->getPass();
        }
    }
    $textNode = $dom->createTextNode('[pass id="' . $idpass . '"]' . $pw . '[/pass]');
    $node->parentNode->replaceChild($textNode, $node);    
}

$content = '';
foreach($dom->getElementById('root')->childNodes as $childNode) {
    $content .= $dom->saveHTML($childNode);
}

libxml_use_internal_errors($state);

echo $content;

demo

Casimir et Hippolyte
  • 88,009
  • 5
  • 94
  • 125