3

I'm writing a plugin for Virtuemart its System type plugin. In their older version (VM2.x) they managed the Div simply with a class and there is no inside Div structure so preg_replace is sufficient for my requirement. But on new version they changed the default Div structure now Div inside Div is there. So preg_replace is unable to replace the whole Div content.

I just used to manage it with PHPDom and Xpath but in some template frameworks (T3) when my plugin enable return an error.

"0 - String could not be parsed as XML"

the same plugin works fine with Gantry and other frameworks. The old version I tried the following code and its works fine on all templates.

$buffer = preg_replace('<div class="ClassNeedstoReplace">([^`]*?)<\/div>/','my custom content',$buffer);    

Now in Vm3 I'm using following codes.

$docs = new DOMDocument();
libxml_use_internal_errors(true);
$docs->resolveExternals = true;
$docs->loadHTML($buffer);
if($getRelatedProduct != '')
$element = $docs->createElement('div', $getRelatedProduct);
$xpath = new DOMXPath($docs);
$query = '//div[@class="'.$class_name.'"]';
$entries = $xpath->query($query);
foreach ($entries as $entry) {

    $entry->parentNode->replaceChild($element, $entry);
}
$docs->recover = true;
libxml_clear_errors();
JResponse::setBody(htmlspecialchars_decode($docs->saveHTML()));

bcoz all the instance of the class needs to be replaced.

I know for testing the plugin required another Extension (VM3).its difficult for debuggers.But simply my requirement is simply replace all the class instance with mine.

is there any way to replace Div inside Div structure without breaking layouts using preg_replace ??

The Div Structure is

<div class="product-fields">
        <div class="product-field product-field-type-R">
                                    <span class="product-fields-title-wrapper"><span class="product-fields-title"><strong>Related Products</strong></span>
                        <span title="" class="hasTooltip" data-original-title="&lt;strong&gt;Related Products&lt;/strong&gt;&lt;br /&gt;COM_VIRTUEMART_RELATED_PRODUCTS_TIP"><img alt="Tooltip" src="/j34/media/system/images/tooltip.png"></span></span>
                <span class="product-field-display"><a target="blank" title="Test Product 1" href="/j34/index.php/shops/c1/t1-detail">Test Product 1</a></span><span class="product-field-desc">Custom prototype for related products</span>            </div>
        </div>

I'm trying to replace "product-field product-field-type-R" it may comes more than one inside "product-fields"

Hope someone will be able to figure out something :)

3
  • Presumably there is another closing tag before the correct </div>? If there is some pattern to what precedes the correct closing tag, you can use that. Commented Jan 10, 2015 at 9:11
  • That didn't work the classes have multiple occurrence! :(
    – Jobin
    Commented Jan 10, 2015 at 16:15
  • @moomoochoo Yes its Gantry, edited.
    – Jobin
    Commented Jan 11, 2015 at 6:43

2 Answers 2

2

Possible other solutions:

2

There are some grey details in your question, so I'll post a solution what I think will do as you desire using DOMDocument and a direct XPath query. Because your question appears to only have one generated element, and you need to remove one or more unwanted elements, my solution will remove every unwanted element except for the first, then replace the first unwanted element with the created element.

The loop will be iterating all <div> tags that contain class values: product-field and product-field-type-R AND have a direct parent <div> that contains a class value of product-fields. contains provides increased flexibility because it will not break if new class values are added.

Code: (Demo)

$html = /* your valid incoming html */;
$getRelatedProduct = "Flashy, shiny, new";

libxml_use_internal_errors(true);
$dom = new DOMDocument; 
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$element = $dom->createElement('div', $getRelatedProduct);

$xpath = new DOMXPath($dom);
$nodes = $xpath->query("//div[contains(@class, 'product-fields')]/div[contains(@class, 'product-field') and contains(@class, 'product-field-type-R')]");
for ($i = $nodes->length - 1; $i >= 0; --$i) {  // when removing nodes from DOM, work in reverse order for stability
    $node = $nodes->item($i);
    if ($i) { 
        //$trailing_whitespace = $node->nextSibling;
        $node->parentNode->removeChild($node);    // remove any qualifying element that IS NOT the first element
        //$trailing_whitespace->parentNode->removeChild($trailing_whitespace);  // optionally remove line returns after removed tags
    } else {
        $node->parentNode->replaceChild($element, $node);  // replace the first element with the generated element
    }
}

echo $dom->saveHTML();

Using DomDocument & XPath makes this solution more robust than regex because even if the 3rd parties decide to restructure the class attribute values, the desired elements will still be matched.

1
  • Nice, straightforward solution!
    – Zollie
    Commented Nov 28, 2018 at 17:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.