Modifying and saving an XML file in Drupal

Pasan Gamage
3 min readJun 3, 2021

Today's use case is to alter content of an existing XML file in my public files directory when a user triggers an add forms save action.

For this to work we need to implement a hook_form_FORM_ID_alter

Let's say the node type the user is editing is called My Files and below is the form display.

The task is to

  • Modify an attribute called guid by concatenating publication date.
  • Add a new attribute called id in to schedules element

The original XML file content looks like this

<?xml version="1.0" encoding="utf-8"?>
<act title="Act 2009" id="act-2009-017" publication.date="2022-05-23">
<coverdata id="cd" guid="_f2e529dd-a9f3-434d-bb84-36748dbe2505"/>
<wrapper>
<front id="frnt" guid="_2ce2d477-94b7-4f8f-9d16-80ea92e096a4">
<shorttitle id="frnt-st" guid="_927cc402-5436-40b3-a2da-1f620c45ef1e">Act 2009</shorttitle>
<longtitle id="frnt-lt" guid="_4bbf8fc9-b3b1-4805-bb91-7b4a16304045">
<block>
<txt break.before="1">Some Text</txt>
</block>
</longtitle>
</front>
</wrapper>
<schedules numbering.style="manual" affected.by.uncommenced="0">
<schedule id="sch.1" guid="_42855c73-aa10-47c4-b9ce-3a77d34c9607"></schedule>
</schedules>
</act>

To tap into this form you will need to find its form ID. You can either find it by inspecting the element via web browser or by placing a breakpoint inside hook_form_alter

In this scenario the form alter code needs to be placed into the modules .module file. My custom module name is xml_migrate, hence the file name will be xml_example.module

Using inspect element, I’ve found the form name to be as node_xml_file_form.

/**
* Alter XML File ADD form to modify the XML content.
*
* Implements hook_form_FORM_ID_alter().
*/
function xml_migrate_form_node_xml_file_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$form['actions']['submit']['#submit'][] = 'xml_migrate_update_xml_file_submit'; //This will be the callback function name
return $form;
}

The above code will add a new action to the submit button and the callback function is called xml_migrate_update_xml_file_submit. We will add our logic there.

/**
* Custom submit callback to rename all guid attributes by concatenating with
* published date.
* And add ID attribute for schedules.
*/
function xml_migrate_update_xml_file_submit(&$form, FormStateInterface $form_state) {
// Helper class have a constant that holds the path name of the file upload directory
$helper = new Helper();
// field_xml_file is the file upload field name in the node
$fid = $form_state->getValue('field_xml_file')[0]['fids'][0];
$file = File::load($fid);
$file_name = $file->getFilename();
$file_path = $helper::XML_FOLDER . '/' . $file_name;
// Converts the XML content to a SimpleXML object.
$xmlObject = simplexml_load_file($file_path);
// Use xpath to target the publication.date attribute $published_date = $xmlObject->xpath('/node()[self::subordleg or self::act]')[0]->attributes()['publication.date'];// Use xpath to target the id attribute
$legislation_id = $xmlObject->xpath('/node()[self::subordleg or self::act]')[0]->attributes()['id'];
// Find the schedules element and set new attribute called id if it do not exist. if (!isset($xmlObject->xpath('//node()[self::schedules]')[0]->attributes()->id)) {
$xmlObject->xpath('//node()[self::schedules]')[0]->addAttribute("id", $legislation_id . '_' . $published_date);
}
// Update all the guid values throughout the document foreach ($xmlObject->xpath('//@guid') as $item) {
$item->guid = $item->guid . '_' . $published_date;
}
// Save the updated object to a new file. In this case I'm overwriting existing file.
$xmlObject->asXML($file_path);
}

And that's it ! We have successfully updated the XML file.

--

--