9

We have an issue where our HTML/CSS requires RTF content to be wrapped in <p> tags in order for some design elements to work correctly.

We had some code which added these in if the string did not start with <p however this is too naive an implementation because it is possible for our editors to add <h2> and other block level elements to these RTFs (wrapping those in <p> tags is invalid markup and most browsers will autoclose to create this <p></p><h2>test</h2>test<p></p>).

Given this:

<h2>test</h2>
test

I want this:

<h2>test</h2>
<p>test</p>

I am struggling to think of an approach to solve this.

Would it be best to use HtmlTidy or similar to parse and "fix" the document? Or can this be achieved with filtering XSLT to add in any missing <p> tags around the inline text?

I guess a final alternative would be to change the schema to lock down the design.

4
  • Could you keep your naive solution of checking for "<p" but add in another condition that looks for <h or other illegal HTML combinations? The idea of parsing HTML reminds me of this: stackoverflow.com/a/1732454/1675729 Commented May 1, 2013 at 10:03
  • Haha yeah, that was a good answer. HtmlTidy is a real parser though it doesn't use RegEx's github.com/markbeaton/TidyManaged. Commented May 1, 2013 at 10:24
  • Which version and browser are you on? 2011 (without SP1) had an issue with different tags for enter and I recall R5.3 not adding p tags until users pressed enter. I actually now get divs after headings as seen in this question: webmasters.stackexchange.com/questions/29505/…. Maybe double check the user steps that create that markup as well? It might be harder than it seems. ;-) Commented May 1, 2013 at 13:11
  • Hi Alvin - 2011 with SP1, Chrome Commented May 1, 2013 at 13:18

5 Answers 5

5

When you know the root node (which is the name of your RTF field basically), I was thinking something along the lines of this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output omit-xml-declaration="yes" method="xml" cdata-section-elements="script"/>
  <xsl:template match="/ | node() | @*">
    <xsl:copy>
      <xsl:apply-templates select="node() | @*"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="body/text()" >
    <p><xsl:copy/></p>
  </xsl:template>
</xsl:stylesheet>

But that doesn't quite cut it, since when your text will contain elements of itself, like for instance a link tag, the <p> will be added around each separate text node (since it is not a single text node anymore.

So this template seems to work fine for (provided your RTF field is called body)

<h2>test</h2>
test

But just doesn't work for:

<h2>test</h2>
test with a <a href="dummy">link</a> in there

I can't seem to find a solution for selecting all text nodes of a certain parent as a single node-set

1
  • @rob-stevenson-leggett maybe you should give this question a try on StackOverflow based on pure XSLT so supply your xml as <body><h2>test</h2>test <a>link</a> test</body> and ask them how to get to <body><h2>test</h2><p>test <a>link</a> test</p></body> Commented May 1, 2013 at 12:50
5

One option (may or may not suits you) I can suggest is to create a guideline for the content authors. The Guideline should states like below:

If you are entering any content into a RTF field, any text which is not enclosed in an HTML tag, should be entered in the "Design" tab of the RTF field, rest all (i.e. those which are enclosed by an HTML tag) should be entered in Source tab of the RTF field.

For your above case <h2>test</h2> should be entered in the Source tab and the plain text -test should be entered in the Design tab of the RTF field, Tridion will automatically add the p tag to it.

Alternatively, you can choose to create your html in the design tab of the RTF field using the options provided over the RTF field.

4
  • Never entertain content authors to use source tab. Since its a bad pratice. We can suggest authors to use format ribbon. Commented May 1, 2013 at 12:59
  • @Siva Charan: I know all these bad (and good) practices, I think you should give this suggestion to the question poster, and if you have read my answer carefully, I am also suggesting the same as an alternate option. If the users which are concerned in this problem are actually using the Ribbon, this issue will never come up. Commented May 1, 2013 at 13:02
  • :) Yes you mentioned as alternative. Just its a suggestion from my side. Commented May 1, 2013 at 13:06
  • Nice and excellent suggestion Siva :), however, it will add more value if you provide it as a comment to the question itself, as here it is simply duplicating, I hope you understand :) Commented May 1, 2013 at 13:10
4

This doesn't seem to be about XSLT, but rather about the RTF implementation. I've been trying out various options using 2013, and there's definitely some quirkiness in there.

If the RTF were being treated as an XML document in the normal sense, then I'd expect the following XSLT to do what you want:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/> 
    <xsl:template match="/">
      <p><xsl:apply-templates/></p>
    </xsl:template>
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Of course, an RTF isn't really an XML document, but rather a fragment thereof, potentially with multiple top-level nodes, so Tridion first creates a document with a root element before the XSLT processing takes place They used to use "body" for this, and I suspect they still do, but I wasn't able to prove it. This used to take place all in the browser, but in 2013 it seems there's a back-end service to take care of the transforms. (Maybe that's a strategy to avoid issues with different browsers?)

In my experiments, "/" doesn't seem to get matched at all, although ISTR I had more success with "/body", or was it "/*". In any case, getting this right using the RTF XSLT is probably more trouble than it's worth.

I'd say, in this case, steer clear of the "filtering" XSLT. What you are after, in any case is correctly templated output. For this, your best bet is probably to read the XML of the RTF into an XML document object (XMLDocument, XDocument, whatever). Then you can simply work with the nodes to inject an xhtml:p at the top if it doesn't exist, and then push it through an XSLT to strip the namespace. Or you could do it all in an XSLT, but at least then you're working with an XML document that you've loaded and that you control. Of course, YMMV for 2011, but perhaps at least if you work with your own DOM, you have a very good expectation that your work will upgrade nicely.

2

We've discovered that pressing "enter" at the end of the line will force Tridion to add a <p> tag. So we are taking a content editor education approach along with a naive implementation which checks for < at the start of the line instead of <p. This seems to be working fairly well.

1

I have the same issue. Rather than fixing it in code on the presentation side, we have the following XSLT which is not perfect, but addresses the case where the editor enters content without hitting enter.

It encloses the content in a <p> tag if the content does not contain any of the elements which would normally automatically close it (based on information on this page)

As such the following 3 examples would be fixed:

This is some plain content

This is some content with a <a href="blah">link</a>

<em>This</em> is content starting and ending with <strong>markup</strong>

But the following will not, it seems to complex to try to separate the phrasing and flow child nodes into groups with XSLT 1.0. This however would have to be either pasted in, or manually entered in the source tab, so its an edge case.

This will <em>not</em> work <p>As it is a mixture of phrasing and flow elements</p>

Here is the XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output omit-xml-declaration="yes" method="xml" cdata-section-elements="script"></xsl:output>
    <xsl:template match="/ | node() | @*">
        <xsl:copy>
            <xsl:apply-templates select="node() | @*"></xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/*">
        <xsl:copy>
            <xsl:choose>
                <xsl:when test="count(.//p|article|aside|blockquote|div|dl|fieldset|footer|form|h1|h2|h3|h4|h5|h6|header|hr|menu|nav|ol|pre|section|table|ul)=0">
                    <p>
                        <xsl:apply-templates></xsl:apply-templates>
                    </p>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates></xsl:apply-templates>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

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.