OK - worked out a brute force solution.
Apparently the parser doesn't mind verbose entities (or whatever they're called) - i.e. '’' for a single quote - IF you double encode the ampersand.
Since my XML has to work in Flash as well as PHP (for the HTML version of the site) I'm writing it all using numeric entities. Then I made a pair of arrays - one numeric entities and one verbose - and use str_replace() to replace them all when using PHP. And then a second str_replace to encode the ampersands.
So '&8217[semicolon]' ends up as '’'
And it works.
Clunky as hell, but it works.
$numerics = array( // of course this board is encoding my numeric entities...
'Á',
'á',
'É',
'é',
'Í',
'í',
'Ó',
'ó',
'Ú',
'ú',
'’'
);
$verbose = array (
'Á',
'á',
'É',
'é',
'Í',
'í',
'Ó',
'ó',
'Ú',
'ú',
'’'
);
$xml = str_replace('&', '&', str_replace($numerics, $verbose, $xml));