1

I am trying to insert a div that'll call an ad between two paragraphs of copy text.

However, I'd like to do it only one instance. The current replace code I'm using is this - which is inserting my test code multiple times.

    {% set str = block.bodyText %}
    {{ str | replace('</p>
    <p>', '</p><div id="test"></div><p>') | smartypants }}

Is it possible?

1

Another way would be to create a Twig filter that does what is explained here replace only first appearance of string

{{ str|replace_once() }} 
1

I can't think of a way to do this with Twig's replace filter but it is possible to do it by splitting the text into an array and iterating over it like so:

{% set content %}
  <p>Duis mollis, est non commodo luctus.</p>
  <p>Nulla vitae elit libero, a pharetra augue.</p>
  <p>Donec sed odio dui. Maecenas faucibus mollis interdum. </p>
{% endset %}

{% set inserted %}
  <p>Inserted text</p>
{% endset %}

{# Split text into array on closing </p> tags #}
{% for paragraph in content | split('</p>') %}
  {# Add the closing tag back, except for possible empty lines  #}
  {% set paragraph = paragraph | trim | length ? paragraph ~ '</p>' : '' %}
  {# Render the text as raw html #}
  {{ paragraph | raw }}
  {# Render the inserted paragraph on the first loop #}
  {% if loop.index == 1 %}
    {{ inserted }}
  {% endif %}
{% endfor %}

Edit: Per your question in the comment about the context, this is a good use case for a macro. This code is untested, but you might try something like this.

{% macro insertAfterFirstParagraph(content, insertContent) %}
  {% for paragraph in content | split('</p>') %}
    {% set paragraph = paragraph | trim | length ? paragraph ~ '</p>' : '' %}
    {{ paragraph | raw }}
    {% if loop.index == 1 %}
      {{ insertContent }}
    {% endif %}
  {% endfor %}
{% endmacro %}

{% from _self import insertAfterFirstParagraph %}

{% set addThisHtml %}
  <p>Add this paragraph</p>
{% endset %}

{% set isAdded = false %}
{% for block in entry.myMatrixField %}
  {% if block.type == 'body' and loop.index > 5 and isAdded == false %}
    {{ insertAfterFirstParagraph(block.bodyField, addThisHtml) }}
    {% set isAdded = true %}
  {% else %}
    {{ block.bodyField }}
  {% endif %}
{% endfor %}

Robin's suggestion to build a custom Twig filter in a plugin (or module) would be the cleanest solution, but the Twig code above should get the job done.

Aside: Craft overrides the default Twig replace filter to use PHP's preg_replace() function but that filter doesn't accept the last two optional arguments, limit and count. If it did, then something like this could have worked:

replace('/<\\/p>\\s+?<p>/', '</p><div id="test"></div><p>', 1)

Maybe a feature request is in order.

2
  • This is excellent in that it has reduced the replacement to once per block, but I obviously should've included more of the code! {% if block.type == "body" %} {% if loop.index > 5 %} Is before the code above - so it's running now for every body block after a certain point. Are there any ways to find the first body block after a certain number of blocks?
    – Emma Swann
    Sep 19 '18 at 14:36
  • I updated the answer, but I have a hunch you need to count how many 'body' blocks the loop encountered. That can be done with a variable you increment every time it hits one. Sep 19 '18 at 18:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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