Until recently I wasn’t using images in my journal entries or project entries. I haven’t really had a need until now and I also didn’t care for the workflow needed to insert images. Although still in beta, Symphony 2 has yet to provide an obvious way to manage images with posts efficiently. The solution is dynamic image placement using a section link and XSLT.
Symphony 2 allows you to link one section to another. Although it is possible to have a number of predefined image upload fields in an entry, it is not possible to associate metadata with those images. The ability to create a section link overcomes this limitation by creating an Images section that allows one to upload images and associate the necessary metadata. This also gets rid of limitation on the number of images that can be uploaded. For these reasons, a devoted section for images is preferred.
To set up dynamic image placement, you must create a section called Images with the following fields:
Most fields should be self-explanatory with the exception of Paragraph and Parent. Paragraph will contain the coordinates of where we want the image inserted. Parent will create a relationship between an image in the Images section and its accompanying text entry from, in my case, the Journal section. Naming this section link field Parent will make it easier for you to relate the Images section to more than one section (in other words, name the section link field from the point of view of the Images section). I find this good practice when naming my section links.
Create a data source for your Journal section if you haven’t already and output a parameter based on System ID. Create a data source using the images’ section as its source. Filter the Parent field with parameter outputted by your Journal section, $ds-journal-entries. This will filter the Images section so that only the relevant image entries are outputted in the XML for a given journal entry.
Make sure to attach the Images section to the pages you wish to display images.
Now let’s turn the focus to our templates. If you’re not familiar with Allen Chang’s Manipulating HTML in XML, you’ll need to familiarize yourself with this brilliant technique. This is the foundation of the method and without utilizing this approach, it will not be possible to use my method of dynamic image placement.
In the pages you wish to implement dynamic image placement, you must create a key named images-by-entry. You want to match all image entries using the section link ID. This should be placed towards the top of your stylesheet before any templates.
<xsl:key name="images-by-entry" match="images/entry" use="parent/@link-id" />
Using Allen’s ninja technique linked above, we need to manipulate p elements to place the images.
<xsl:template match="p" priority="0">
<xsl:param name="position"><xsl:number /></xsl:param>
<p>
<xsl:for-each select="key('images-by-entry', ../../@id)">
<xsl:if test="paragraph = $position">
<img alt="{description}" src="{$workspace}{image/@path}/{image/filename}" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="* | @* | text()"/>
</p>
</xsl:template>
First, we need to create a parameter to store the position of the p element. It’s worth noting that you cannot use position() for our comparison because it takes into account all elements and whitespace which results in an unexpected position. Next, we use key() to grab all the images that have a link-id equal to the current entry’s id that is being matched and loop through them. Finally, we compare the position of the p element to the paragraph field of each Images entry and if it matches we insert the image.
You may have already noticed that if you have two textarea fields in your Journal section then you will end up with images being inserted twice, once for each paragraph position in each field. Instead of inputting just a number for our paragraph field, we can use the Bingo approach and utilize a letter and a number. The letter corresponds to the field and the number corresponds to the paragraph, thereby eliminating the duplicate insertion. In the example below, we accommodate two textarea fields, body and more.
<xsl:template match="p" priority="0">
<xsl:param name="position"><xsl:number /></xsl:param>
<xsl:param name="parent" select="name(..)" />
<p>
<xsl:for-each select="key('images-by-entry', ../../@id)">
<xsl:choose>
<xsl:when test="$parent = 'body' and substring(paragraph, 1, 1) = 'b' and substring(paragraph, 2) = $position">
<img alt="{description}" src="{$workspace}{image/@path}/{image/filename}" />
</xsl:when>
<xsl:when test="$parent = 'more' and substring(paragraph, 1, 1) = 'm' and substring(paragraph, 2) = $position">
<img alt="{description}" src="{$workspace}{image/@path}/{image/filename}" />
</xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates select="* | @* | text()"/>
</p>
</xsl:template>
Now, if we want to place an image in the body field in the first paragraph, we would simply type ‘b1’ in to the paragraph field. At this point, you may choose to yell Bingo!
If you have other templates that manipulate the p element they may make conflict and the one with higher priority will prevail. For example, I manipulate the last element in my body field to insert a link to the entry’s individual page if there is content in the more field. So, if the last element in the body field is a p element then my image insert template is overridden by the link insert template. Although the template above generally has a low priority because it matches every p element, I have added a priority attribute to make sure that it is interpreted with a lower priority. You could increase this number so the situation I just described with my two templates is reversed; it’s a personal preference which is more important and depends on the situation, but you must choose one or the other. The workaround is to not add images to the last paragraph in each field.