Updating XML Files in the Sitecore Media Library Programmatically

Recently, I had a requirement to automatically update some XML files in the Media Library to add in attributes for images uploaded to Sitecore when another item (that referenced an XML file) was updated.

In order to do this, I needed to create an event on item saved that took the referenced XML file, formatted the required images’ URLs and added the URLs as attribute values into the XML file’s child nodes.

The approach here was quite simple. On the item saved event, I added a field that pointed to a particular Media Library folder where the images were stored, and retrieved a list of the image URLs. Thankfully, these images had a naming convention from a legacy version of the website that roughly mapped to an “id” attribute value of the XML child nodes, so I used those as the indicator to decide what images went where.

Next, I took the XML Media Library file, retrieved the file stream and mapped it to an XmlDocument object in code.

I then looped through each XML node in the document, created a new “images” attribute and proceeded to concatenate the attribute value with each image URL that matched the naming convention of the node.

Finally, I appended each new attribute to its respective node, saved the document and updated the Media Library version of the XML file with the newly updated stream.

This is just one approach you can take, and can be expanded or altered in numerous different ways and for different file types as needed. In my case, the items in question don’t get updated too frequently, so I didn’t validate if updates were needed (aka if there were no new images to add), but you could if you wanted to. You could also add in publishing functionality if necessary.

Below is some sample code. Depending on your setup, you may want to alter the event triggers, related items and logic. And as always, consider the need and implementation of any item events in Sitecore as it’s easy to negatively impact performance.

UpdateXmlDocument.cs

using System;
using System.IO;
using System.Linq;
using System.Xml;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Events;
using Sitecore.Links.UrlBuilders;
using Sitecore.Resources.Media;
using Sitecore.XA.Foundation.SitecoreExtensions.Extensions;

namespace Namespace
{
	public class UpdateXmlDocument
	{
		public void OnItemSaved(object sender, EventArgs args)
		{
			// Get the item being saved.
			var currentItem = Event.ExtractParameter<Item>(args, 0);

			// Set your trigger, aka what types of changed items should execute the following code.
			// I only want to trigger against items of a certain template, but this can be anything.
			if (currentItem != null && currentItem.TemplateName == "TEMPLATE NAME")
			{
				// Get the field that references the folder containing all of the images.
				var imagePathField = (ReferenceField)currentItem.Fields["Image Folder"];

				if (imagePathField != null && !imagePathField.Value.IsNullOrWhiteSpace())
				{
					// Grab the target item (the image folder item).
					var imagePathItem = imagePathField.TargetItem;

					if (imagePathItem != null)
					{
						// Get all items that use a media library image template, or at least the ones you want.
						var images = imagePathItem.Axes.GetDescendants().Where(x => x.TemplateName == "Jpeg" || x.TemplateName == "Image").Select(x => (MediaItem)x);

						if (images.Any())
						{
							// If there are any images, we'll pull in the field referencing the XML file.
							var xmlFileField = (FileField)currentItem.Fields["XML File"];

							if (xmlFileField != null)
							{
								var xmlFileItem = xmlFileField.MediaItem;

								if (xmlFileItem != null)
								{
									var xmlFileMedia = MediaManager.GetMedia(xmlFileItem);

									if (xmlFileMedia != null)
									{
										using (StreamReader reader = new StreamReader(xmlFileMedia.GetStream().Stream))
										{
											XmlDocument document = new XmlDocument();
											document.Load(reader);

											XmlNodeList childNodes = document.SelectNodes("/MAIN NODE/CHILD NODE");

											// Set the appropriate site.
											var site = Sitecore.Configuration.Factory.GetSite("SITE NAME");

											if (site != null)
											{
												// Get the site, and any other URL properties you may need.
												// In this case, we're using SXA so I'm grabbing the value in the MediaLinkServerUrl
												var siteName = site.Name;
												var database = Sitecore.Configuration.Factory.GetDatabase("master");
												var siteGroupingItem = database.GetItem("/sitecore/content/TENANT/WEBSITE/Settings/Site Grouping/" + siteName);
												
												if (siteGroupingItem != null)
												{
													// Grab the media link server url and strip the scheme.
													var mediaLinkServerUrl = siteGroupingItem["MediaLinkServerUrl"].Replace("https://","").Replace("http://","");

													if (!mediaLinkServerUrl.IsNullOrWhiteSpace())
													{
														var mediaLinkServerUrlParts = mediaLinkServerUrl.Split('/');

														var mediaLinkServerDomain = mediaLinkServerUrlParts[0];
														var mediaLinkServerPath = "";

														// If the media link server url contains more than just a domain, grab the path. This will be used in our URI builder path property.
														if (mediaLinkServerUrlParts.Length > 1)
														{
															mediaLinkServerPath = mediaLinkServerUrl.Replace(mediaLinkServerDomain, "");
														}

														foreach (XmlNode childNode in childNodes)
														{
															// Add the images attribute, regardless of if there are images or not.
															var imagesAttribute = document.CreateAttribute("images");
															var idAttribute = childNode.Attributes["id"];

															if (idAttribute != null && !idAttribute.Value.IsNullOrWhiteSpace())
															{
																var nodeImages = images.Where(x => x.Name.Contains(idAttribute.Value)).ToList();
																if (nodeImages.Any())
																{
																	var imagePaths = "";
																	
																	// Loop through each relateved image grab it's media URL and append it to the attribute value.
																	foreach (var image in nodeImages)
																	{
																		var uri = new UriBuilder()
																		{
																			Scheme = Uri.UriSchemeHttps,
																			Host = mediaLinkServerDomain,
																			Path = mediaLinkServerPath + image.GetMediaUrl(new MediaUrlBuilderOptions() { AlwaysIncludeServerUrl = false })
																		};

																		imagePaths += uri.Uri + ",";
																	}
																	
																	// This is specific to our instance, you likely don't need to do this.
																	imagesAttribute.Value = imagePaths.Trim(',').Replace("/sitecore/shell/-/media", "");
																	
																	// Finally, add the new images attribute to the node.
																	childNode.Attributes.Append(imagesAttribute);
																}
															}
														}
													}
												}
											}

											// Set the newly modified XML to a memory stream.
											var xmlStream = new MemoryStream();
											document.Save(xmlStream);
											
											// Finally, update the media library XML file.
											xmlFileMedia.SetStream(xmlStream, ".xml");
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
}

Events.config

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
	<sitecore>
		<events>
			<event name="item:saved">
				<handler type="Namespace.UpdateXmlDocument, Namespace" resolve="true" method="OnItemSaved" />
			</event>
		</events>
	</sitecore>
</configuration>

Leave a comment