As you already noted in your question, Tidy operates on a full HTML document, so a repaired one contains (if configured) a doctype but always a head section.
You're looking for a HTML fragment, which is not a full HTML document.
This differentiation already contains the solution: Inject your HTML fragment as the body, repair it as a document and then obtain only the children of the body as string. Done. Here is a rather short but already working example:
/* Tidy example: Clean a HTML fragment */
$fragment = '<b><s>Text</b>';
$tidy = new Tidy;
$tidy->parseString($fragment);
$tidy->cleanRepair();
$result = implode('', (array) $tidy->body()->child); // <b><s>Text</s></b>
If you want to go a step further and validate the HTML fragment (e.g. only valid HTML tags and all tags closed, see as well tidy configuration), you can prefix it with the bare minimum HTML so errors and warnings fall into the responsibility of the fragment itself. The status can be obtained then via tidy::getStatus()
:
/* Tidy example: Validate a HTML fragment (4 samples) */
$fragments = [
['Valid fragment', '<b><s>Text</s></b>'],
['Unclosed tag', '<b><s>Text</b>'],
['Unknown tag', '<unknown></unknown>'],
['Unfinished attribute', '<a href=">link</a>'],
];
$tidy = new Tidy;
foreach ($fragments as list($title, $fragment)) {
$tidy->parseString("<!DOCTYPE html><title>HTML fragment</title><body>" . $fragment);
$status = $tidy->getStatus();
$tidy->cleanRepair();
$result = implode('', (array)$tidy->body()->child);
printf("%s - Status: %d\n HTML: %s\n Repaired: %s\n", $title, $status, $fragment, $result);
}
This example will provide the following output demonstrating the meaning of the return value of getType
in the default configuration:
Valid fragment - Status: 0
HTML: <b><s>Text</s></b>
Repaired: <b><s>Text</s></b>
Unclosed tag - Status: 1
HTML: <b><s>Text</b>
Repaired: <b><s>Text</s></b>
Unknown tag - Status: 2
HTML: <unknown></unknown>
Repaired:
Unfinished attribute - Status: 2
HTML: <a href=">link</a>
Repaired: <a href="%3Elink%3C/a%3E"></a>
That's more than you've asked for and I only added it as an additional example, e.g. to see if all tags in a string are closed.