Yes, as per SAX spec, the characters() event gets unescaped text (also startElement()'s attributes). So the code inside the content handler that writes out the text must escape it.
If someone is interested, I have an xhtml conform content handler (that also makes HTML4-compatible XHTML, normal serializers just produce XML or HTML4), that is based off XALAN/XERCES serializer.jar, but adds some filtering, so the outputted XHTML has correct block tags, has space before "/>", and e.g. always separates start/end tag of some elements like <script></script> even if they are empty (because only attributes set). It also makes style/script always use CDATA with a faked comment to make this even compatible with HTML4, where SCRIPT/STYLE is defined to be CDATA by default (adding fake comments).
You only need to add a proper DOCTYPE currently (its not added to this handler for our internal purposes, because we also serialize fragments usig that class, so it omits doctypes).
I attached this document handler, maybe it is of some use for somebody. It needs serializer.jar from XERCES/XALAN to compile and work.
This is currently as far as I know the only XHTML serializer that produces XHTML that is in generall needed to make XHTML documents behave correctly even with browsers that only support HTML4. It passes both HTML4 and XHTML1 validators (of course the element data needs to be correct).