descendants from
* body_html. True (the default) is correct
* for the single-page pipeline, where
* Nav_Mapper turns $doc->nav_html into a
* wp_nav_menu() call in header.php instead
* — leaving the original markup in
* body_html too would render the nav
* twice. A multi-page conversion
* (Multi_Page_Rewriter) passes false: its
* navs vary structurally page to page (in
* the real export this was built against,
* the homepage's nav used in-page anchor
* links while every other page's used real
* page links) and don't fit Nav_Mapper's
*
/- -or-direct--children
* assumptions at all, so each page keeps
* its own nav embedded in its own content
* instead of one shared wp_nav_menu() call.
* Silently stripping it there — as
* discovered against a real export — also
* corrupts the exported HTML enough to
* break the page's own client-side
* hydration, not just visually drop the nav.
*
* @return Parsed_Document
*/
public function parse( $html, $strip_nav = true ) {
$dom = new \DOMDocument();
$previous_setting = libxml_use_internal_errors( true );
// The XML prolog trick forces DOMDocument to treat the input as
// UTF-8 without requiring mbstring-based re-encoding.
$dom->loadHTML(
'' . $html,
LIBXML_NOWARNING | LIBXML_NOERROR
);
libxml_clear_errors();
libxml_use_internal_errors( $previous_setting );
$xpath = new \DOMXPath( $dom );
$doc = new Parsed_Document();
$doc->title = $this->extract_title( $xpath );
$doc->meta = $this->extract_attribute_list( $xpath, '//meta' );
$doc->links = $this->extract_attribute_list( $xpath, '//link' );
$doc->scripts = $this->extract_scripts( $xpath );
$doc->nav_html = $this->extract_nav_html( $xpath );
$doc->body_html = $this->extract_body_html( $xpath, $strip_nav );
$doc->stylesheets = array_values(
array_filter(
$doc->links,
function ( $attrs ) {
return isset( $attrs['rel'] ) && 'stylesheet' === strtolower( $attrs['rel'] );
}
)
);
return $doc;
}
/**
* Extract the document text.
*
* @param \DOMXPath $xpath Query interface.
*
* @return string
*/
private function extract_title( \DOMXPath $xpath ) {
$nodes = $xpath->query( '//title' );
if ( ! $nodes || 0 === $nodes->length ) {
return '';
}
return trim( $nodes->item( 0 )->textContent );
}
/**
* Collect every attribute of every node matching $query, in document
* order, as a list of assoc arrays.
*
* @param \DOMXPath $xpath Query interface.
* @param string $query XPath expression.
*
* @return array[]
*/
private function extract_attribute_list( \DOMXPath $xpath, $query ) {
$results = array();
$nodes = $xpath->query( $query );
if ( ! $nodes ) {
return $results;
}
foreach ( $nodes as $node ) {
$results[] = $this->node_attributes( $node );
}
return $results;
}
/**
*