7 Days to Die Wiki
Advertisement

Introduced in Alpha 17, new modding hooks were made available to manipulate XML without directly modifying the base XML files. This new mechanism makes use of XPath, which is an expression syntax for picking out nodes (or sets of nodes) in XML, as well as a few XML modification commands which act on the nodes chosen by the Xpath expression.

This page will go over some of the basics of XPath syntax as well as the 7D2D XML modification commands.

What is XPath?[ | ]

XPath is an expression syntax that is used to select nodes and attributes in an XML Document. XPath does not do anything to the XML, it only selects nodes and attributes.

Very briefly, a node (or "element") in a well-formed XML document begins with a tag like "<my_node>" and ends with a closing tag "</my_node>" (the node includes the opening and closing tags as well as anything between them). A node may also be self-closing, with "/>" at the end of the opening tag and no closing tag. Every XML document has a "root" node which contains all other nodes. A node which is contained within another node is called a "child" node, the container is called its "parent" node, and all nodes at the same level are "siblings". Within a node's opening tag there can be attributes which show up as name='value' pairs.

Here is a simplified example from 7D2D's items.xml configuration file. <items> is the root node and it has two <item> child nodes (which are siblings to eachother). Both of the <item> nodes have two <property> child nodes. The <item> and <property> nodes have attributes like "name" and "value". (Note that in 7D2D, all useful information is stored as an attribute. So you will never see <item_class>tool</item_class>, instead it will be <item class="tool"/>.)

<items>
  <item name="meleeToolRepairT0StoneAxe">
    <property name="Tags" value="axe,melee,light,tool"/>
    <property name="DisplayType" value="meleeRepairTool"/>
  </item>
  <item name="meleeToolRepairT3Nailgun">
    <property name="Tags" value="tool,nailgun,sideAttachments"/>
    <property name="DisplayType" value="rangedRepairTool"/>
  </item>
</item>

An XPath expression is used to navigate through an XML document, selecting nodes and attributes that you want to work with. Within 7 Days to Die's modding framework, XPath is used to identify the nodes you want to change or remove, or the point where you want to add your own nodes. The game has implemented several XML modification commands, discussed in detail below, which take XPath expressions as parameters.

For more information on XML documents and syntax, visit this page at w3schools.com

For more information on XPath, visit this page at w3schools.com

This forum post from the A17 era provided much of the information at the heart of this page

XPath Syntax[ | ]

The following examples show the basics of finding a node (or set of nodes) or an attribute (or set of attributes), including the use of conditionals which should cover most of what you'd need to do for basic 7D2D mods. See the w3schools.com link above for a full discussion of XPath expressions. Note that 7D2D may not support all standard XPath expressions.

All XPath expressions are case-sensitive. If your mod isn't working, double-check that you capitalize nodes and attributes exactly as in the 7D2D xml files.

The examples will use this very simplified (and slightly fake) example from 7D2D's items.xml:

<items>
  <item name="StoneAxe">
    <property name="Tags" value="axe,melee,light,tool"/>
    <property name="Material" value="Mstone"/>
    <property name="Weight" value="5"/>
  </item>

  <item name="Nailgun">
    <property name="Tags" value="tool,nailgun,sideAttachments"/>
    <property name="Weight" value="7"/>
    <property class="Action0">
      <property name="Reload_time" value="2"/>
    </property>
  </item>
 
   <item name="Shotgun">
    <property name="Tags" value="weapon,ranged"/>
    <property name="Weight" value="12"/>
    <property class="Action0">
      <property name="Reload_time" value="3.9"/>
    </property>
  </item>
</items>

Selecting a set of nodes[ | ]

Selecting a set of nodes in XPath is as simple as giving the path from the root node down to your desired level:

xpath="/items/item"

That XPath expression would select all <item> nodes under the root <items> node, meaning that the StoneAxe, Nailgun, and Shotgun <item> nodes would all be selected. Note that without any conditions, you will select all nodes which match the level in your XPath expression. For example if we use this expression:

xpath="/items/item/property"

We will select all <property> nodes which are direct children of an <item> node. So you'd get the "Tags", "Material", and "Weight" <property> nodes from the StoneAxe, as well as "Tags", "Weight", and "Action0" from the Nailgun and Shotgun. It would not select the "Reload_time" <property> nodes because they are not direct children of <item> nodes. XPath selects all matching nodes at the level you specify anywhere in the XML document. See the 'Search All Levels' token below for a way to select all nodes regardless of level in the hierarchy.

Selecting a set of attributes[ | ]

You can select a set of attributes by adding '@' to the attribute name at the end of your XPath expression.

xpath="/items/item/@name"

That XPath expression would select all "Name" attributes from all <item> nodes in the example, so the result set would include name="StoneAxe", name="Nailgun", and name="Shotgun". Just like when selecting nodes, without any condition to limit the results, you will get all matching attributes anywhere in the XML document at the level you specify.

The 'Search All Levels' Token[ | ]

To search at all levels of the XML hierarchy you can insert '//' into your expression, which means "search anywhere below this spot regardless of level". As we showed above, if you wanted to get <property> nodes which are direct children of <item> nodes, you would use:

xpath="/items/item/property"

But that doesn't match the "Reload_time" <property> nodes because they are one level deeper. If you want all <property> nodes under any <item>, no matter how deep in the hierarchy they are, then you use '//':

xpath="/items/item//property"

That says "start at every <item> node under the <items> root node, and select all <property> nodes no matter how many levels deep they are." For the example XML above (as well as for the real 7D2D items.xml), this selects all <property> nodes everywhere in the XML.

The 'Parent' Token[ | ]

The parent token, '..', is useful when you need to find (and change) a node which has some attribute on one of its child nodes. You select the child node using a conditional (more details on these below) and then back up one node to the parent.

xpath="/items/item/property[@class='Action0']/..

That expression will find <property> nodes where the "class" attribute equals 'Action0' and then back up one level to select the parent <item> node. For the example XML, the result is to select the Nailgun, and Shotgun <item> nodes since those two have "Action0" <property> child nodes.

XPath Conditionals[ | ]

In 99% of cases you will want to work on a specific node or at least a carefully chosen subset. That is where conditionals come in. Since everything descriptive in 7D2D XML is put into an attribute, you will nearly always be using conditionals on attributes, i.e. looking for a particular item name, or zombie type, etc.

Equal To[ | ]

By far the most-used conditional will be "equal to":

xpath="/items/item[@name='StoneAxe']"

Note that the conditional value - 'StoneAxe' - must be put in single quotes. Note also the slight difference between selecting an attribute and filtering on (applying a conditional to) an attribute. When we selected attributes earlier, we put the attribute name at the end of the path with an @ sign:

/items/item/@name    (SELECTING attributes)

However when we are applying a conditional, we need to put the attribute right next to the node which contains it and put it in square brackets.

/items/item[@name='...']    (APPLYING CONDITIONAL to attribute)

You do not have to stop your XPath expression after you have applied a conditional - in fact most times you won't, because you probably want to make a change to some other attribute or sub-node. For example:

(1) xpath="items/item[@name='StoneAxe']/property"
(2) xpath="items/item[@name='StoneAxe']/property[@name='Tags']"
(1) will select all <property> nodes for the item with @name="StoneAxe", so you'd get the "Tags", "Material", and "Weight" <property> nodes in your result set.
(2) will specifically select the <property name="Tags"> node from the <item name="StoneAxe"> node.

Greater Than/Less Than[ | ]

The greater than/less than conditionals work as you would expect, but due to XML syntax rules it isn't as simple as using < and >. The problem is that '<' is not a valid character within an XML attribute and, when doing 7D2D XML modding commands (later on this page) your XPath expression is given in an attribute:

<append xpath="your_xpath_expression_goes_here">...</append>

So that means instead of '<' you have to use '&lt;'. For greater than, you can use '>' if you wish, but you may want to use '&gt;' instead, for consistency. Some examples:

(1a) xpath="/items/item/property/property[@value &gt; 2]"
(1b) xpath="/items/item/property/property[@value > 2]" (alternate syntax using '>')
(2) xpath="/items/item/property/property[@value &lt; 4]"
(1a) and (1b) will select all <property> nodes which are children of a <property> node which is a child of an <item> node, where the "value" attribute is greater than (&gt;) 2. This would select the "Reload_time" property node of the Nailgun item's "Action0" property.
(2) will select all <property> nodes which are children of a <property> node which is a child of an <item> node, where the "value" attribute is less than (&lt;) 4. This would select the "Reload_time" propertie nodes for both the Nailgun and the Shotgun.

And, Or, and Not[ | ]

You can combine conditions with "and" and "or" within the square brackets.

(1)  xpath="/items/item/property[@name='Weight' or @name='Tags']"
(2)  xpath="/items/item/property/property[@name='Reload_time' and @value &lt; 4]"
(1) selects all <property> nodes beneath an <item> node where the @name attribute is either "Weight" or "Tags".
(2) selects all <property> nodes beneath a <property> node beneath an <item> node where the property node's @name attribute is "Reload_time" and the @value is less than 4.

Note in (2) we still explicitly spelled out the full path to the <property> we wanted: /items/item/property/property. But now we can use the "search anywhere" token (//) described above and make that expression more flexible:

xpath="/items/item//property[@name='Reload_time' and @value &lt; 4]"

That expression starts at every <item> node and finds any <property> node anywhere underneath it where the @name is "Reload_time" and the @value is less than 4. Even if the layout of 7D2D's XML changes somewhat, as long as there is a "Reload_time" property node somewhere between <item> and </item>, the XPath expression will still work.

You can also use "not" to select nodes where the condition is not matched:

xpath="/items/item[not @name='Nailgun']"

That will select any <item> nodes except the Nailgun item, i.e. the StoneAxe and Shotgun nodes. Finally, using parentheses within the conditional expression, you can combine and, or, and not for very complex selections.

xpath="items/item//property[(@name='Reload_time' or @name='Weight') and (not @value &lt; 5)]"

String Searches[ | ]

There are three useful string-comparison conditionals: starts-with(s1,s2), ends-with(s1,s2), and contains(s1,s2). In all three, s1 is the string being searched and s2 is the value you are testing for. In almost all cases with 7D2D XML mods, s1 will be an attribute like @name or @value. Of course you can use "not" with any of these to select non-matching nodes.

(1) xpath="items/item[starts-with(@name,'S')]"
(2) xpath="items/item[ends-with(@name,'gun')]"
(3) xpath="items/item//property[@name='Tags' and contains(@value,'tool')]/.."
(4) xpath="items/item//property[@name='Tags' and not contains(@value,'tool')]/.."
(1) selects all <item> nodes where @name starts with "S", matching the "StoneAxe" and "Shotgun" <item> nodes.
(2) selects all <item> nodes where @name ends with "gun", matching the "Nailgun" and "Shotgun" <item> nodes.
(3) selects all <property> nodes where @name="Tags" and @value contains the string "tool", then backs up one level in the hierarchy to grab the parent <item> nodes. Read this as "select all <item> nodes where the Tags <property> has the word "tool" in it". The selected nodes would be the StoneAxe and Nailgun <item> nodes.
(4) is the converse of (3) - it selects all <item> nodes where the child "Tags" <property> node does not have "tool" in its @value attribute. The selected node would be the Shotgun <item> node.

XML Modification Commands[ | ]

7D2D's XML mod engine supports several XML modification commands which use the XPath expression syntax above to select nodes and attributes for changes. The examples below will use the following simplified (and slightly fake) example from items.xml.

<items>
  <item name="StoneAxe" category="axe">
    <property name="Tags" value="axe,melee,light,tool"/>
    <property name="Material" value="Mstone"/>
    <property name="Weight" value="5"/>
  </item>
  <item name="Nailgun" category="repair">
    <property name="Tags" value="tool,nailgun,sideAttachments"/>
    <property name="Weight" value="7"/>
    <property class="Action0">
      <property name="Reload_time" value="2"/>
    </property>
  </item>
</items>

Changing an Attribute (set)[ | ]

The set command is used to change an existing attribute on a node. If the named attribute does not exist it does not create it and a warning will be sent to the log, but it will not prevent the mod from loading.

(1) <set xpath="/items/item[@name='StoneAxe']/property[@name='Weight']/@value">3</set>
(2) <set xpath="/items/item/property[@name='Weight']/@value">3</set>
(1) changes the Weight value for the StoneAxe from 5 to 3.
(2) would change all Weight properties to 3, because there is no conditional on the <item> nodes! Probably not something you'd ever want to do, but included here as an example of how XPath selects nodes.

Adding or Changing an Attribute (setattribute)[ | ]

The setattribute command can be used to add a new attribute to a node or to change an existing attribute just like the 'set' command above.

(1) <setattribute xpath="/items/item/property[@name='Weight']" name="measure">kilograms</setattribute>
(2) <setattribute xpath="/items/item[@name='StoneAxe']/property[@name='Weight']" name="value">3</setattribute>
(1) adds a new attribute called "measure" with a value of "kilograms" to all <property> nodes with @name='Weight'. The resulting XML for the StoneAxe would look like this (Nailgun would look similar):
<item name="StoneAxe">
  <property name="Tags" value="axe,melee,light,tool"/>
  <property name="Material" value="Mstone"/>
  <property name="Weight" value="5" measure="kilograms"/>
</item>
(2) changes the existing Weight value for the StoneAxe from 5 to 3, just like example (1) from the 'set' command.

Removing Nodes and Attributes (remove, removeattribute)[ | ]

The remove command is used to remove nodes from the XML. If the node does not exist, a warning will be sent to the log, but it will not prevent the mod from loading.

(1) <remove xpath="/items/item[@name='Nailgun']/property[@name='Tags']" />
(2) <remove xpath="/items/item[@name='Nailgun']" />
(1) removes the Tags <property> from the Nailgun item.
(2) removes the Nailgun <item> node completely.

The removeattribute command is used to remove an attribute from an XML node. If the attribute does not exist, a warning will be sent to the log, but it will not prevent the mod from loading. The following would remove the "category" attribute from the StoneAxe <item> node:

<removeattribute xpath="/items/item[@name='StoneAxe']/@category" />

The resulting <item> node would look like this:

<item name="StoneAxe">

Adding Nodes and Attribute Values (append)[ | ]

The append command can be used to add entire nodes or to add values onto the end of an existing attribute. If we wanted to add an <item> node for a shotgun into the example XML above, we could use this command:

<append xpath="/items">
  <item name="Shotgun" category="gun">
    <property name="Tags" value="weapon,ranged"/>
    <property name="Weight" value="12"/>
    <property class="Action0">
      <property name="Reload_time" value="3.9"/>
    </property>
  </item>

Everything between <append> and </append> will be added to the end of the nodes under the <items> root node and it would look like this:

<items>
  <item name="StoneAxe" category="axe">
    <property name="Tags" value="axe,melee,light,tool"/>
    <property name="Material" value="Mstone"/>
    <property name="Weight" value="5"/>
  </item>
  <item name="Nailgun" category="repair">
    <property name="Tags" value="tool,nailgun,sideAttachments"/>
    <property name="Weight" value="7"/>
    <property class="Action0">
      <property name="Reload_time" value="2"/>
    </property>
  </item>
  <item name="Shotgun" category="gun">
    <property name="Tags" value="weapon,ranged"/>
    <property name="Weight" value="12"/>
    <property class="Action0">
      <property name="Reload_time" value="3.9"/>
    </property>
  </item> 
</items>

If we want to add an additional item into the @value attribute of to the Tags property of the Shotgun item, we could use this:

<append xpath="/items/item[@name='Shotgun']/property[@name='Tags']/@value">,MyNewTag</append>

Note that the leading comma is required in that case - the XML command appends exactly what you type. The resulting <property> node would be:

<property name="Tags" value="weapon,ranged,MyNewTag"/>

Adding Nodes Where You Want Them (insertAfter, insertBefore)[ | ]

Unlike append, which adds nodes always to the end of the list (within the parent node), insertAfter will add nodes directly after the node(s) selected by your XPath expression. The new node(s) are added as 'siblings' at the same level as the node(s) from your XPath.

<insertAfter xpath="/items/item[name='Nailgun']/property[name='Tags']">
  <property name="AfterTags" value="tag1,tag2,tag3"/>
</insertAfter>

The above command will add the AfterTags <property> node into the Nailgun's <item> node. Note that even though we specified a self-closing tag - <property ... /> - the XML mod engine actually expands it to use <property> and </property> opening/closing tags (along with a comment in between). This is functionally identical and does not change the effects of your XML modification.

<item name="Nailgun" category="repair">
  <property name="Tags" value="tool,nailgun,sideAttachments"/>
  <property name="AfterTags" value="tag1,tag2,tag3"><!--Element inserted by: "YourMod"--></property>
  <property name="Weight" value="7"/>
  <property class="Action0">
    <property name="Reload_time" value="2"/>
  </property>
</item>

The insertBefore command works exactly the same as insertAfter - it just puts your new node(s) before the node(s) selected by your XPath expression.

<insertBefore xpath="/items/item[name='Nailgun']/property[name='Tags']">
  <property name="BeforeTags" value="tag1,tag2,tag3"/>
</insertBefore>

The results of the above:

<item name="Nailgun" category="repair">
  <property name="BeforeTags" value="tag1,tag2,tag3"><!--Element inserted by: "YourMod"--></property>
  <property name="Tags" value="tool,nailgun,sideAttachments"/>
  <property name="Weight" value="7"/>
  <property class="Action0">
    <property name="Reload_time" value="2"/>
  </property>
</item>

Advanced XPath Node Selectors[ | ]

There are a few more advanced XPath operators which can be useful in some cases. The examples will use this very simplified (and slightly fake) example from 7D2D's items.xml:

<items>
  <item name="StoneAxe">
    <property name="Tags" value="axe,melee,light,tool"/>
    <property name="Weight" value="5"/>
    <property name="Material" value="Mstone"/>
  </item>
  <item name="Nailgun">
    <property name="Tags" value="tool,nailgun,sideAttachments"/>
    <property name="Weight" value="7"/>
  </item>
   <item name="Shotgun" category="gun">
    <property name="Tags" value="weapon,ranged"/>
    <property name="Weight" value="12"/>
  </item>
</items>

Selecting First/Last Node Among Siblings[ | ]

To select the first node among siblings, just put [1] after the node in your XPath expression. This example will put a new <property> node after the "Tags" <property> node for all <item> nodes.

<insertAfter xpath="/items/item/property[1]"><property name="MyProperty"></insertAfter>

To select the last node among siblings, use the last() predicate. The example below will add a new <property> above the "Material" property for the StoneAxe and above the "Weight" properties for the Nailgun and Shotgun.

<insertBefore xpath="/items/item/property[last()]"><property name="MyProperty"></insertBefore>

Selecting Nodes at Specific Relative Positions[ | ]

To select nodes at specific points - e.g. the 4th node, or 3rd from the last - you can use a combination of the [n] and last() syntax shown above as well as the position() predicate.

(1a) <insertBefore xpath="/items/item[@Name='StoneAxe']/property[2]"><property name="MyProperty"></insertBefore>
(1b) <insertBefore xpath="/items/item[@Name='StoneAxe']/property[last()-1]"><property name="MyProperty"></insertBefore>
(2) <setattribute xpath="/items/item[@Name='StoneAxe']/property[position() &gt; 1]" name="foo">bar</setattribute>
(1a) and (1b) accomplish the same thing - inserting a new <property> node before the "Weight" <property> node of the StoneAxe item. (1a) does it by explicitly selecting the 2nd <property> node as the target of insertBefore. (1b) does it by using "last()-1" which means "one node before the last node".
(2) uses the position() predicate and the greater than (&gt;) operator to select all <property> nodes where postion > 1, that is, everything after the first <property>. It then adds an attribute "foo" with value "bar" to all matching nodes. The resulting XML would be:
<items>
  <item name="StoneAxe">
    <property name="Tags" value="axe,melee,light,tool"/>
    <property name="Weight" value="5" foo="bar"/>
    <property name="Material" value="Mstone" foo="bar"/>
  </item>
  <item name="Nailgun">
    <property name="Tags" value="tool,nailgun,sideAttachments"/>
    <property name="Weight" value="7" foo="bar"/>
  </item>
   <item name="Shotgun" category="gun">
    <property name="Tags" value="weapon,ranged"/>
    <property name="Weight" value="12" foo="bar"/>
  </item>
</items>

Selecting Nodes Which Have a Specific Attribute[ | ]

This is a special case of the [@attribute='value'] syntax covered at the start. By simply removing the ='value' part of the expression, you can select for all nodes which have the named attribute.

<remove xpath="/items/item[@category]" />

That command would remove any <item> node which has a "category" attribute which for the example XML would mean the Shotgun <item> node gets removed.

Example - Infinite Horde[ | ]

This is a working example which modifies gamestages.xml from A18/A19 so that the last wave of the Blood Moon Horde lasts all night. Here is a simplified snippet from gamestages.xml, which is the file that controls the horde spawns:

<gamestages>
  <spawner name="BloodMoonHorde">
		<gamestage stage="1">
      <spawn group="feralHordeStageGS1" num="2" maxAlive="3" duration="1"/>
    </gamestage>
		<gamestage stage="10">
      <spawn group="feralHordeStageGS7" num="15" maxAlive="6" duration="1" interval="24"/>
      <spawn group="feralHordeStageGS10" num="15" maxAlive="6" duration="1"/>
    </gamestage>
  </spawner>
</gamestages>

We want to make two changes to that XML. First, we need to set the "num" attribute on the last wave to a very high number - 99999 - so that the zombies never run out. Second, we need to eliminate the "duration" attribute of the last wave so that it doesn't time out before horde night ends at 4AM. By using the last() predicate, we can make these changes with two commands:

<set xpath="/gamestages/spawner[@name='BloodMoonHorde']/gamestage/spawn[last()]/@num">99999</set>
	<removeattribute xpath="/gamestages/spawner[@name='BloodMoonHorde']/gamestage/spawn[last()]/@duration" />

The resulting XML would be:

<gamestages>
  <spawner name="BloodMoonHorde">
		<gamestage stage="1">
      <spawn group="feralHordeStageGS1" num="99999" maxAlive="3"/>
    </gamestage>
		<gamestage stage="10">
      <spawn group="feralHordeStageGS7" num="15" maxAlive="6" duration="1" interval="24"/>
      <spawn group="feralHordeStageGS10" num="99999" maxAlive="6"/>
    </gamestage>
  </spawner>
</gamestages>
Advertisement