UltraMega Blog
14Jul/093

Generating (X)HTML Documents Using DOMDocument In PHP

PHP 5 includes a powerful set of DOM manipulation classes that gives you full control over XML documents. This functionality behaves very similar to JavaScript's DOM manipulation engine. In this tutorial, we'll explore the DOMDocument class by generating an entire (X)HTML page without writing a single bit of raw markup. This may not be practical for most applications, but it should give you a good idea of how the basic DOMDocument methods work.

First, let's look at what the final output should look like. The generated version will have slightly different formatting and indentation, but the functionality will be the same.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>DOMDocument</title>
<link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="wrapper">
  <div id="header"><img src="header.gif" alt="Header" width="400" height="100" /></div>
  <div id="nav">
    <ul>
      <li class="active"><a href="index.php">Home</a></li>
      <li><a href="download.php">Download</a></li>
      <li><a href="features.php">Features</a></li>
      <li><a href="about.php">About</a></li>
    </ul>
  </div>
  <div id="content">
    <h1>DOMDocument</h1>
    <p>This page was generated using PHP's <strong>DOMDocument</strong> class!</p>
  </div>
</div>
</body>
</html>

As you can see, this is a very basic template with a header, navigation, and content sections. The header includes a single image, the navigation includes a list of links, and the content has a heading and any page content. All three sections are also wrapped in a "wrapper" div. Of course, all the page styling will be handled by the included CSS file in the header.

So now let's start writing code! Below each snippet is an explanation.

1
2
3
4
5
6
7
8
9
<?php
// Create document
$doctype = DOMImplementation::createDocumentType('html',
                  '-//W3C//DTD XHTML 1.1//EN',
                  'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd');
 
$document = DOMImplementation::createDocument('http://www.w3.org/1999/xhtml',
                   'html',
                   $doctype);

Here is where we setup the document, using a special class called DOMImplementation that allows us to easily create a document with the appropriate DOCTYPE declaration. The createDocumentType method stores the document type information in an object to be passes to the createDocument method, which actually creates the document. This document has the root <html> tag already created.

If you compare the call to createDocumentType to the <!DOCTYPE line of the XHTML, you can see where the 3 parameters come from. The createDocument class accepts the value of the xmlns attribute of the html tag, the name of the root element (<html>), and the object created by createDocumentType as parameters.

11
12
// Create head element
$head = $document->createElement('head');

Here is an example of creating a simple element using the createElement method, in this case it is the head tag of the document. The createElement class accepts either one or two parameters. The first, and required, one is the name of the element to create. If a second parameter is supplied, it will create a text node of the supplied text and append it to the created element.

14
15
16
17
18
19
20
21
22
23
24
25
26
$metahttp = $document->createElement('meta');
$metahttp->setAttribute('http-equiv', 'Content-Type');
$metahttp->setAttribute('content', 'text/html; charset=utf-8');
$head->appendChild($metahttp);
 
$title = $document->createElement('title', 'DOMDocument');
$head->appendChild($title);
 
$css = $document->createElement('link');
$css->setAttribute('href', 'styles.css');
$css->setAttribute('rel', 'stylesheet');
$css->setAttribute('type', 'text/css');
$head->appendChild($css);

More examples of creating elements, as well as actually adding them to the document with the DOMNode::appendChild method. This method can be called on any node created with createElement, and accepts the node that you want to add as its parameter. For example, here we created a $title element, which we added to the $head element.

This segment also demonstrates setting attributes with the DOMElement::setAttribute method, which simply accepts the attribute name and value as parameters.

28
29
30
31
32
33
// Create body element
$body = $document->createElement('body');
 
// Wrapper div
$wrapper = $document->createElement('div');
$wrapper->setAttribute('id', 'wrapper');

Here we created the body tag and the wrapper div.

35
36
37
38
39
40
41
42
43
44
45
46
// Header div
$header = $document->createElement('div');
$header->setAttribute('id', 'header');
$wrapper->appendChild($header);
 
// Header img
$himg = $document->createElement('img');
$himg->setAttribute('src', 'header.gif');
$himg->setAttribute('alt', 'Header');
$himg->setAttribute('width', '400');
$himg->setAttribute('height', '100');
$header->appendChild($himg);

Here, we created the header div and added the header image to it.

48
49
50
51
52
53
54
55
// Nav div
$nav = $document->createElement('div');
$nav->setAttribute('id', 'nav');
$wrapper->appendChild($nav);
 
// Nav ul
$navlist = $document->createElement('ul');
$nav->appendChild($navlist);

Here is where the nav div and list are created.

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
$menuArray = array();
$menuArray['index.php'] = 'Home';
$menuArray['download.php'] = 'Download';
$menuArray['generate.php'] = 'Generate';
$menuArray['about.php'] = 'About';
 
$currentPage = basename($_SERVER['SCRIPT_FILENAME']);
 
foreach($menuArray as $page => $title) {
    $navitem = $document->createElement('li');
    if($page == $currentPage) {
        $navitem->setAttribute('class', 'active');
    }
 
    $navlink = $document->createElement('a', $title);
    $navlink->setAttribute('href', $page);
 
    $navitem->appendChild($navlink);
    $navlist->appendChild($navitem);
}

Here's the fun part! First, we set up an array of navigation links and figured out the file name of the current page. Then, we looped through the array to create and append the list items and links to the nav list. We also check to see if the item is the current page, and add the "active" class if it is.

78
79
80
81
82
83
84
85
// Content div
$content = $document->createElement('div');
$content->setAttribute('id', 'content');
$wrapper->appendChild($content);
 
// Actual page content
$h1 = $document->createElement('h1', 'DOMDocument');
$content->appendChild($h1);

Here is where we start creating the content area. We created the content div and added a heading tag.

87
88
89
90
91
92
93
94
$p = $document->createElement('p');
$text = $document->createTextNode('This page was generated using PHP\'s ');
$p->appendChild($text);
$text = $document->createElement('strong', 'DOMDocument');
$p->appendChild($text);
$text = $document->createTextNode(' class!');
$p->appendChild($text);
$content->appendChild($p);

Here is where we create the paragraph element with text. In this case, since the text has a strong tag within the text, we need to break the text into pieces. First, we create the text before, then the strong text, and then the remaining text. I don't know of a better way to do this other than automating the process of breaking up text.

96
97
98
99
100
101
$body->appendChild($wrapper);
 
// Add head and body to document
$html = $document->getElementsByTagName('html')->item(0);
$html->appendChild($head);
$html->appendChild($body);

Here, we actually add the wrapper div to the body, and the head and body elements to the document. In order to access the root tag, we use getElementsByTagName and grab the first item (since there is only one, the first is the correct one). If you know of a better way to access the root element, please comment!

103
104
105
106
// Output document
$document->formatOutput = true;
echo $document->saveXML();
?>

Here is where we finally output the document in text form to the browser using the saveXML method. I also set the formatOutput property to true so it indents the code nicely.

Note: I used saveXML instead of saveHTML since XHTML documents follow some of the same standards as XML, such as self-closing tags using <tag />.

See the live demo page...

Similar Posts:

 

About Steve

Steve is the owner of UltraMega Tech. He is a freelance Web designer and developer who specializes in PHP and AJAX development.
Tagged as: , , Leave a comment
Comments (3) Trackbacks (1)
  1. Hey Steve,
    I want to first thank you for the great example here! Documentation for DOMDocument is scarce and it’s really hard to find any conclusive articles on generating XHTML with it, so your article was very useful.
    I will note that the static calls to createDocument() and createElement() will trigger a Strict warning in PHP 5.3 since they aren’t static methods, but that’s an easy fix.

    The problem I’m having is when generating some form field types, mainly the textarea. textarea fields must have a closing tag “” and cannot be closed like some of the others. Unfortunately, it doesn’t seem saveXML() realizes this and therefore creates incorrect textarea tags. I haven’t tried any of the others, so I’m not sure, but “input” fields work fine and so does “form”.

    Do you have any ideas on this?
    Thanks.

    • I spoke too soon …
      After taking another look at it and looking at how the form element had proper tags, I had an idea and it worked. Here’s what I did:
      $textarea = $document->createElement(‘textarea’);
      $textarea->setAttribute(‘cols’, ’50′);
      $textarea->setAttribute(‘rows’, ’5′);
      $textarea->setAttribute(‘name’, ‘comments’);
      $blank = $document->createTextNode(”);
      $textarea->appendChild($blank);

      By adding a blank textnode to it, it made it create both tags. Simple fix.

      Anyways, thanks again!

    • When you want to create an empty textarea, you just need to set its contents to an empty string. $textarea = $document->createElement('textarea', ''); should produce the correct results.

      Edit: Looks like we posted at the same time. What I posted is basically a shortcut to adding a text node.


Leave a comment


Page optimized by WP Minify WordPress Plugin