Completed
Push — add/amp-pwa-experiment ( efea12 )
by
unknown
11:53
created

AMP_DOM_Utils   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 102
rs 10
c 0
b 0
f 0
wmc 19
lcom 1
cbo 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A get_dom_from_content() 0 19 2
A get_content_from_dom() 0 15 2
A create_node() 0 5 1
A get_node_attributes_as_assoc_array() 0 11 3
A add_attributes_to_node() 0 5 2
A is_node_empty() 0 4 2
B recursive_force_closing_tags() 0 21 5
A is_self_closing_tag() 0 13 2
1
<?php
2
3
class AMP_DOM_Utils {
4
	public static function get_dom_from_content( $content ) {
5
		$libxml_previous_state = libxml_use_internal_errors( true );
6
7
		$dom = new DOMDocument;
8
		// Wrap in dummy tags, since XML needs one parent node.
9
		// It also makes it easier to loop through nodes.
10
		// We can later use this to extract our nodes.
11
		// Add utf-8 charset so loadHTML does not have problems parsing it. See: http://php.net/manual/en/domdocument.loadhtml.php#78243
12
		$result = $dom->loadHTML( '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $content . '</body></html>' );
13
14
		libxml_clear_errors();
15
		libxml_use_internal_errors( $libxml_previous_state );
16
17
		if ( ! $result ) {
18
			return false;
19
		}
20
21
		return $dom;
22
	}
23
24
	public static function get_content_from_dom( $dom ) {
25
		// Only want children of the body tag, since we have a subset of HTML.
26
		$out = '';
27
		$body = $dom->getElementsByTagName( 'body' )->item( 0 );
28
29
		// AMP elements always need closing tags.
30
		// To force them, we can't use saveHTML (node support is 5.3+) and LIBXML_NOEMPTYTAG results in issues with self-closing tags like `br` and `hr`.
31
		// So, we're manually forcing closing tags.
32
		self::recursive_force_closing_tags( $dom, $body );
33
34
		foreach ( $body->childNodes as $node ) {
35
			$out .= $dom->saveXML( $node );
36
		}
37
		return $out;
38
	}
39
40
	public static function create_node( $dom, $tag, $attributes ) {
41
		$node = $dom->createElement( $tag );
42
		self::add_attributes_to_node( $node, $attributes );
43
		return $node;
44
	}
45
46
	public static function get_node_attributes_as_assoc_array( $node ) {
47
		$attributes = array();
48
		if ( ! $node->hasAttributes() ) {
49
			return $attributes;
50
		}
51
52
		foreach ( $node->attributes as $attribute ) {
53
			$attributes[ $attribute->nodeName ] = $attribute->nodeValue;
54
		}
55
		return $attributes;
56
	}
57
58
	public static function add_attributes_to_node( $node, $attributes ) {
59
		foreach ( $attributes as $name => $value ) {
60
			$node->setAttribute( $name, $value );
61
		}
62
	}
63
64
	public static function is_node_empty( $node ) {
65
		return false === $node->hasChildNodes()
66
			&& empty( $node->textContent );
67
	}
68
69
	public static function recursive_force_closing_tags( $dom, $node ) {
70
		if ( XML_ELEMENT_NODE !== $node->nodeType ) {
71
			return;
72
		}
73
74
		if ( self::is_self_closing_tag( $node->nodeName ) ) {
75
			return;
76
		}
77
78
		if ( self::is_node_empty( $node ) ) {
79
			$text_node = $dom->createTextNode( '' );
80
			$node->appendChild( $text_node );
81
			return;
82
		}
83
84
		$num_children = $node->childNodes->length;
85
		for ( $i = $num_children - 1; $i >= 0; $i-- ) {
86
			$child = $node->childNodes->item( $i );
87
			self::recursive_force_closing_tags( $dom, $child );
88
		}
89
	}
90
91
	private static function is_self_closing_tag( $tag ) {
92
		// This function is called a lot; the static var prevents having to re-create the array every time.
93
		static $self_closing_tags;
94
		if ( ! isset( $self_closing_tags ) ) {
95
			// https://www.w3.org/TR/html5/syntax.html#serializing-html-fragments
96
			// Not all are valid AMP, but we include them for completeness.
97
			$self_closing_tags = array(
98
				'area', 'base', 'basefont', 'bgsound', 'br', 'col', 'embed', 'frame', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr',
99
			);
100
		}
101
102
		return in_array( $tag, $self_closing_tags );
103
	}
104
}
105