the \n is for new line, not null. If you use default tsv mode - each new line is represented as \n to distinguish difference between new line and new row, eg:
t=# copy(select query_to_xml('select datname,null blah from pg_database limit 2',false,true,'')) to stdout;
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n <datname>postgres</datname>\n</row>\n\n<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n <datname>t</datname>\n</row>\n\n
So simply csv option will rescue you, eg:
t=# copy(select query_to_xml('select datname,null blah from pg_database limit 2',false,true,'')) to stdout csv;
"<row xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">
<datname>postgres</datname>
</row>
<row xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">
<datname>t</datname>
</row>
"
https://www.postgresql.org/docs/current/static/sql-copy.html
COPY TO will terminate each row with a Unix-style newline (“\n”).
and so on...
update
copying to stdout or file does not matter:
t=# copy(select query_to_xml('select datname,null blah from pg_database limit 2',false,true,'')) to '/tmp/sp.xml' csv;
COPY 1
Time: 0.481 ms
t=# \! cat /tmp/sp.xml
"<row xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">
<datname>postgres</datname>
</row>
<row xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">
<datname>t</datname>
</row>
"
with null as ' 'works for me