DEV Community

André Laugks
André Laugks

Posted on

Use XInclude to organize Content elements in Page templates in SULU

With SULU, the content elements are defined with content types in the page templates. This can become confusing in a complex implementation with many page templates and content elements.

The keyword is reusability in order to avoid effort, errors and inconsistencies. For the name of the property for a text area, developers like to use bodytext, copytext or text.

A content element is, for example, an input field - like a headline - that stands alone or several input fields that are combined with a <block/>.

For this article SULU 2.5 is run in a Docker environment with PHP 8.2 (libxml 2.9.10).

The code examples are available on Github.

Content elements integrated in the page template

Here is an example where all content elements are defined directly in the page template.

To reduce the complexity for this article, I use a page template article.xml with the two content elements headline (content type: text_line) and bodytext (content type: text_editor).

<?xml version="1.0" ?>
<template 
   xmlns="http://schemas.sulu.io/template/template"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
   <!-- ... -->
   <properties>
      <!-- ... -->
      <property name="headline" type="text_line">
         <meta>
            <title lang="en">Headline</title>
         </meta>
      </property>
      <property name="bodytext" type="text_editor">
         <meta>
            <title lang="en">Bodytext</title>
         </meta>
      </property>
   </properties>
</template>
Enter fullscreen mode Exit fullscreen mode

Content element in include files

To better organise the individual include files, I created the directory include within the templates directory. How you organise and name the include files is up to you.

Now the content elements are each transferred to the file headline.xml and bodytext.xml and stored in the directory include.

%kernel.project_dir%/config/templates/include/headline.xml

<?xml version="1.0" ?>
<properties xmlns="http://schemas.sulu.io/template/template"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
    <property name="headline" type="text_line">
        <meta>
            <title lang="en">Headline</title>
        </meta>
    </property>
</properties>
Enter fullscreen mode Exit fullscreen mode

%kernel.project_dir%/config/templates/include/bodytext.xml

<?xml version="1.0" ?>
<properties xmlns="http://schemas.sulu.io/template/template"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
    <property name="bodytext" type="text_editor">
        <meta>
            <title lang="en">Bodytext</title>
        </meta>
    </property>
</properties>
Enter fullscreen mode Exit fullscreen mode

In order to be able to use the content elements in article.xml, XInclude is used. XInclude is a recommendation of the W3C to refer to XML or text documents within XML documents in order to replace the content with the referenced file.

%kernel.project_dir%/config/templates/pages/article.xml

<?xml version="1.0" ?>
<template xmlns="http://schemas.sulu.io/template/template"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:xi="http://www.w3.org/2001/XInclude"
          xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
    <!-- ... -->
    <properties>
        <!-- ... -->
        <xi:include href="../include/headline.xml"
                    xpointer="xmlns(sulu=http://schemas.sulu.io/template/template) xpointer(/sulu:properties/sulu:property)">
            <xi:fallback>An error occurred</xi:fallback>
        </xi:include>
        <xi:include href="../include/bodytext.xml"
                    xpointer="xmlns(sulu=http://schemas.sulu.io/template/template) xpointer(/sulu:properties/sulu:property)">
            <xi:fallback>An error occurred</xi:fallback>
        </xi:include>
    </properties>
</template>
Enter fullscreen mode Exit fullscreen mode

I do not want to go into great detail in this article about how XML-Namespaces, XInclude, XPointer and XPath work, however, the principle is quite simple.

The default namespace in the include files is specified with the attribute xmlns, because only tags from SULU are used in the XML documents. The page template has the same default namespace, however, these have nothing to do with each other.

<xi:include href="%REFERENCE%" xpointer="xmlns(%PREFIX%=%URI%) xpointer(%XPATH%)" />
Enter fullscreen mode Exit fullscreen mode

%REFERENCE% is the relative reference to the file to be included. Then we define the %PREFIX% and %URI% of the namespace. With XPointer it is not the prefix but the URI that is important. In the example I use sulu, but it could also be banana. For the URI we use the default namespace from the include files. For %XPATH% we use another XPath expression to find the element.

All content elements in an include file

XPath gives us the possibility to find elements in the documents. We could therefore store all content elements in a document and find them with an XPath expression using the name of elements or properties.

%kernel.project_dir%/config/templates/pages/elements.xml

<?xml version="1.0"?>
<properties xmlns="http://schemas.sulu.io/template/template"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
    <property name="headline" type="text_line">
        <meta>
            <title lang="en">Headline</title>
        </meta>
    </property>
    <property name="bodytext" type="text_editor">
        <meta>
            <title lang="en">Bodytext</title>
        </meta>
    </property>
</properties>
Enter fullscreen mode Exit fullscreen mode

%kernel.project_dir%/config/templates/pages/article.xml

<?xml version="1.0" ?>
<template xmlns="http://schemas.sulu.io/template/template"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:xi="http://www.w3.org/2001/XInclude"
          xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
    <!-- ... -->
    <properties>
        <!-- ... -->

        <!-- Example: Complete path with attribute -->
        <xi:include href="../include/elements.xml"
                    xpointer="xmlns(sulu=http://schemas.sulu.io/template/template) xpointer(/sulu:properties/sulu:property[@name = 'headline'])">
            <xi:fallback>An error occurred</xi:fallback>
        </xi:include>

        <!-- Example: Find element only by attribute -->
        <xi:include href="../include/elements.xml"
                    xpointer="xmlns(sulu=http://schemas.sulu.io/template/template) xpointer(//*[@name = 'bodytext'])">
            <xi:fallback>An error occurred</xi:fallback>
        </xi:include>
    </properties>
</template>
Enter fullscreen mode Exit fullscreen mode

Observation

In another tutorial, in preparation for this article, I saw an example with XInclude which does not work in my development environment. In the tutorial, no namespace and XML schema were defined in the file to be included and no xpointer was defined in the article.xml in <xi:include/>.

Content element

<?xml version="1.0" ?>
<property name="headline" type="text_line">
    <meta>
        <title lang="en">Headline</title>
    </meta>
</property>
Enter fullscreen mode Exit fullscreen mode

Page template

<?xml version="1.0" ?>
<template xmlns="http://schemas.sulu.io/template/template"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:xi="http://www.w3.org/2001/XInclude"
          xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
    <!-- ... -->
    <properties>
        <!-- ... -->
        <xi:include href="../include/headline.xml"/>
    </properties>
</template>
Enter fullscreen mode Exit fullscreen mode

The following exception is provided:

Uncaught PHP Exception Symfony\Component\Config\Util\Exception\XmlParsingException: "[ERROR 1871] Element 'property': This element is not expected. (in /home/wwwroot/config/templates/pages/article.xml - line 2, column 0)" at /home/wwwroot/vendor/symfony/config/Util/XmlUtils.php line 95 {"exception":"[object] (Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException(code: 0): [ERROR 1871] Element 'property': This element is not expected. (in /home/wwwroot/config/templates/pages/article.xml - line 2, column 0) at /home/wwwroot/vendor/symfony/config/Util/XmlUtils.php:95)"} []
Enter fullscreen mode Exit fullscreen mode

The exception "[ERROR 1871] Element 'property': This element is not expected. (in /home/wwwroot/config/templates/pages/article.xml - line 2, column 0)" is not from Symfony or SULU, but from libxml which implemented PHP.

I have not looked further into this error message or debugged it because on the one hand the XInclude declaration seems unclean and on the other hand the autocompletion of the IDE cannot be used due to the missing XML schema definition in the include files.

My suspicion is that the developer was using an older version of libxml and this might have caused it to work. This is because not all XML processors and versions of these XML processors always fully support the standard defined by the W3C.

Fallback

I have used <xi:fallback /> in my examples, but it is an optional element. I use it because it also helps with debugging, it is cleaner and it tells users that the include is not working.

Because XML processors do not have to provide an error message if the file cannot be included.

Top comments (1)

Collapse
 
opctim profile image
Tim Nelles • Edited

Great article!

Error 1871 is actually caused by the way how the schema of the sulu templates is layed out.
You can fix it by doing the following (note the "xpointer" part in the xi:include):

config/templates/pages/homepage.xml

[...]
<block name="modules" default-type="text" minOccurs="1">
    <meta>
        <title lang="de">Module</title>
    </meta>

    <types>
        <xi:include 
            href="../../modules/text.xml" 
            xpointer="xmlns(sulu=http://schemas.sulu.io/template/template)xpointer(/sulu:properties/sulu:block/sulu:types/sulu:type)"
        />
    </types>
</block>
[...]
Enter fullscreen mode Exit fullscreen mode

config/modules/text.xml

<?xml version="1.0" ?>
<properties xmlns="http://schemas.sulu.io/template/template"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
    <block name="modules" default-type="text">
        <types>
            <type name="text">
                <meta>
                    <title lang="de">Text</title>
                </meta>

                <properties>
                    <property name="text" type="text_editor">
                        <meta>
                            <title lang="de">Text</title>
                        </meta>

                        <tag name="sulu.search.field" />
                    </property>
                </properties>
            </type>
        </types>
    </block>
</properties>
Enter fullscreen mode Exit fullscreen mode

For more info, see also: