Jan 13, 2025
Enterprise websites often consist of hundreds of pages divided into sections or subsections to make it easier for visitors to navigate to their desired content. The content and data on these websites multiply when heavy personalization occurs across multiple user segments, regions, languages, and countries.
Marketing and development teams aim to reuse content as much as possible to reduce the number of areas to be updated. With Adobe Experience Manager (AEM), Content Fragments and Experience Fragments make reusing content easier.
However, in managing all of these different content and page variations, there will be times when developers need to inject systemic logic into each web response to scrub or alter the HTML before it gets sent back to the browser. For example:
-
If they change or add a CDN URL and need it to be prepended to all image URLs
-
If they want to add a version number to all URLs to help manage caching
-
If they want to change all references to a CSS class on the fly to alter the user experience in response to a click from an email
This logic could be placed in many different places, but it should be applied to each response. There is a special way to manage these things centrally and easily remove them later. In this blog, we’ll explore how the Sling Rewriter Transformer allows developers to do this.
What Is the Sling Rewriter Transformer?
The Sling Rewriter Transformer is a powerful tool that allows developers to rewrite the output generated by the Sling rendering process.
Transformers are placed in the middle of the pipeline, which takes the output of the Sling rendering process and writes it into the response stream right after the HTML is generated and just before it is serialized.
Generator: Receives the output from the Sling resolution process and outputs SAX events in XML.
Transformer: Receives the SAX events from the previous pipeline component (generator or another transformer) and outputs SAX events to the following pipeline component.
Serializer: Receives final SAX events in the pipeline, transforms them, and writes them into the response’s output stream.
Use Case: Rewrite URL Using a Sling Rewriter Transformer
Imagine an external third-party website (TPW) URL used by content authors on many site pages being part of different rich text paragraphs (so there’s no single update point).
And the TPW is giving a special weekend discount to your site’s users, for which a special TPW-provided parameter (and value) will have to be added to all links in the site pointing to the TPW URL.
Plus, a special style (CSS class) is usually applied to links you want to highlight, such as those referring to the TPW URL.
The requirements statement is simple: update the links to the TPW URL and apply a specific CSS style.
However, implementing the TPW URL by authoring page content can be messy. Let’s say your site has hundreds of pages using the TPW URL. This would mean hundreds of content updates that need to be rolled back after Sunday, when the promotion ends. The CSS class will also need to be applied to highlight the TPW URL in every place it was used and then rolled back.
This work can be achieved more cleanly using a Sling Rewriter Transformer. The transformer transforms the HTML of all pages, rewrites all links to the TPW URL to point to the new URL with the special parameter, and applies a special CSS class to the link.
Generic Steps to Implement a Custom Sling Rewriter Transformer
1. Create a Custom Sling Transformer
-
Define which information is required for the instantiation of the transformer (constructor parameters)
-
Override the
startElement
method and implement the HTML tag transformation there.
2. Create a Custom Sling Transformer Factory
-
Define a
pipeline.type
and configure(annotate) the Sling Transformer Factory with it. -
Define an OSGi configuration for the Sling Transformer Factory.
-
Override the
createTransformer
method and instantiate the Custom Sling Transformer with the OSGi configuration values. -
Create a configuration file with the required config values to be used.
3. Create a Sling Rewriter configuration
-
Point it to
pipeline.type
of the the Sling Transformer Factory using the transformertransformerTypes
tag. -
Define where the the Sling Transformer Factory will run using the
paths
tag. -
Define which HTML tags the transformation will apply to using the
includeTags
subtag of thegenerator-htmlparser
tag.
4. Verify Custom Sling Transformer is registered and rungs correctly
-
Build the AEM project.
-
Navigate to
http://[host]/system/console/status-slingrewriter
and check if the custom transformer has an entry with the correct information configured. -
Open any page where the transformer should run, check the source code to find the changes applied.
Implement a Custom Sling Rewriter Transformer Step by Step
1. Creating a Custom Sling Transformer
-
Required information:
-
A flag to know if the transformation should occur:
boolean isEnabled
-
The URL to be replaced, to look only for link having it:
String urlToReplace
-
A list of the new attributes to the updated for the
<a>
tag, in this case at least the href and the class attributes:String[] newAttributes
-
-
Override the
startElement
method:-
If the flag
isEnabled
is true and there are values atnewAttributes
then check if the current tag contains the URL that wants to be updated. If so, make updates accordingly and return updated attributes. Otherwise, return the original ones. -
Attributes Format →
name:value
. For example,att1:value1
will add/update attribute'att1'
to'value1'
.@Slf4j public class CustomHtmlTransformer extends DefaultTransformer { public static final String ATT_SEPARATOR = ":"; public static final String CDATA = "CDATA"; boolean isEnabled; protected List
newAttributes; protected String urlToReplace; public CustomHtmlTransformer(boolean isEnabled, String urlToReplace, String[] newAttributes){ if(null!=newAttributes) { this.isEnabled = isEnabled; this.urlToReplace = urlToReplace; this.newAttributes = Arrays.asList(newAttributes); log.error("URL be updated ----> [{}]", this.urlToReplace); log.error("New Attributes to be added/updated ----> [{}]", this.newAttributes.toString()); } else { this.newAttributes = new ArrayList< />(); } } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { log.info("----> startElement: localName:[{}] - attributes:[{}]", localName, attributes.getLength()); if (isEnabled && isNotEmpty(urlToReplace)) { AttributesImpl attributesList = getUpdatedAttributes(uri, attributes); super.startElement(uri, localName, qName, attributesList); } else { super.startElement(uri, localName, qName, attributes); } } private AttributesImpl getUpdatedAttributes(String uri, Attributes attributes) { AttributesImpl attributesList = new AttributesImpl(attributes); boolean requiresUpdate = requiresUpdate(attributes); if(requiresUpdate && !newAttributes.isEmpty()) { this.newAttributes.forEach( att -> { if (isNotEmpty(att) && att.length() >= 3 && att.contains(ATT_SEPARATOR)) { String[] attAsArray = att.split(ATT_SEPARATOR); String attName = attAsArray[0]; String attValue = attAsArray[1]; int attPosition = attributesList.getIndex(attName); if (attPosition >= 0) { log.info("Updating attribute {}:{}", attName, attValue); attributesList.setAttribute(attPosition, uri, attName, attName, CDATA, attValue); } else { log.info("Adding attribute {}:{}", attName, attValue); attributesList.addAttribute(uri, attName, attName, CDATA, attValue); } } } ); } return attributesList; } private boolean requiresUpdate(Attributes attributes) { boolean requiresUpdate = false; int hrefIndex = attributes.getIndex("href"); if(hrefIndex>0) { String currentUrl = attributes.getValue(hrefIndex); if (isNotEmpty(currentUrl)) { if (currentUrl.contains(urlToReplace)) { requiresUpdate = true; } } } return requiresUpdate; } }
-
2. Create a Custom Sling Transformer Factory
-
Define a
pipeline.type
: custom-html-transformer -
OSGi configuration :
@interface Config
-
Override the
createTransformer
method and return a new Custom Transformer with the OSGi configuration@Slf4j @Component( immediate = true, service = TransformerFactory.class, property = { "pipeline.type=custom-html-transformer" } ) @Designate(ocd = CustomHtmlTransformerFactory.Config.class) public class CustomHtmlTransformerFactory implements TransformerFactory { private String[] newAttributes; private String urlToReplace; private boolean isEnabled; @Activate protected void activate(Config config){ newAttributes = config.attributes(); urlToReplace = config.urlToReplace(); isEnabled = config.isEnabled(); } @ObjectClassDefinition( name = "URL Replacement Config", description = "Configuration for URL to be replaced for tags defined at /apps/aem-challenge/config/rewriter/custom-html-transformer" ) @interface Config{ @AttributeDefinition( name = "URL Replacement Enabled", description = "Is the URL replacement enabled?" ) boolean isEnabled() default false; @AttributeDefinition( name = "List of Attributes to be created/updated", description = "Attributes Format -> name:value. For example: att1:value1 will set 'att1' to 'value1'." ) String[] attributes() default {"att1:value1"}; @AttributeDefinition( name = "URL To be Updated", description = "The URL that is required to be Updated" ) String urlToReplace() default "urlToBeReplaced"; } @Override public Transformer createTransformer() { log.debug("----> CreateTransformer CustomHtmlTransformer!"); return new CustomHtmlTransformer(isEnabled, urlToReplace, newAttributes); } }
-
Create a configuration file at
apps/[aem_project_folder]/osgiconfig/[run_mode]
with the required config values (remember to name it accordingly likeapps/[aem_project_folder]/osgiconfig/config/com.SUB_PACKAGE_NAMES.CustomHtmlTransformerFactory~href-update.cfg.json)
{ "isEnabled": "true", "urlToReplace": "[EXISTING_URL_TO_BE_REPLACED]", "attributes": [ "href:NEW_URL?WITH_PARAMETER", "class:CSS_CLASS_NAME_TO_BE_APPLIED" ] }
3. Create a Sling Rewriter configuration
Create a new configuration file at apps/[aem_project_folder]/config/rewriter/custom-html-transformer.xml
(create directory apps/[aem_project_folder]/config/rewriter/
if required) and configure accordingly:
-
Point it to
pipeline.type
of the the Sling Transformer Factory:transformerTypes="[linkchecker,custom-html-transformer]"
-
Define the
paths
tag:paths="[/content/aem_project_path]"
-
Define to which HTML tags the transformation will apply:
includeTags="[A,/A]"
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primaryType="nt:unstructured" contentTypes="[text/html]" enabled="true" extensions="[html]" generatorType="htmlparser" order="1" serializerType="htmlwriter" transformerTypes="[linkchecker,custom-html-transformer]" paths="[/content/AEM_PROJECT_PATH]" > <generator-htmlparser jcr:primaryType="nt:unstructured" includeTags="[A,/A]"/> </jcr:root>
4. Verify the Custom Sling Transformer is registered and runs correctly
-
Build the AEM project
-
Go to
http://localhost:4502/system/console/configMgr
, search for the “URL Replacement Config” and check the configuration has been loaded correctly: -
Open
http://localhost:4502/system/console/status-slingrewriter
and verify there’s a configuration for the rewriter like this:Name : custom-html-transformer Content Types : [text/html] Paths : [/content/[AEM_PROJECT_PATH]] Extensions : [html] Process Error Response : true Order : 1 Active : true Valid : true Pipeline : Generator : htmlparser : {includeTags=[A, /A]} Transformers : linkchecker custom-html-transformer Serializer : htmlwriter Resource path: /apps/[AEM_PROJECT_FOLDER]/config/rewriter/custom-html-transformer
-
Open any page where the link exists, open the source code, and find the URL and the CSS class updated:
<a class="" data-cmp-clickable="" href="NEW_URL?WITH_PARAMETER" target="_blank">
Wrapping Up
Sling Rewriter Transformers is a powerful tool for transforming the HTML output of websites before they are delivered. It allows users to change the already-defined page’s DOM structure, which is easier for enterprise websites with hundreds or thousands of pages.
If you’re using AEM and want to make changes to enterprise websites using Sling Rewriter Transformers, Oshyn is available to help. Our extensive knowledge of AEM can help you streamline workflows and improve your customer experience.
Learn more about AEM in our Adobe Experience Manager Implementation Best Practices guide.
References
Related Insights
-
-
-
Jonathan Saurez
Unit Testing Using AEM Mocks
Detecting Code Flaws Before They Cause Outages for Your Users
-
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.