解析結果をきちんとエスケープしてXMLで出力する

前回のコードには、正当な入力にも関わらず、例えば不等号記号などが入っていると、
誤った形式のXMLを出力してしまうバグがあることに気が付いた。
入力に含まれている文字のうち事前定義された実体がある5種類の文字については、
それが出力すべき文字列に含まれるなら置換するよう修正する。

translator.y
%{
...snip
static char *estrdupcat(const char *s1, const char *s2);
static char *escape(const char *s);
%}
...snip
string_headed :
      string_except_parenthesis { char *s = escape($1); fputs(s, stdout); free(s); free($1); }
    | string_except_parenthesis { char *s = escape($1); fputs(s, stdout); free(s); free($1); } marked_string_headed
    ;
...snip
marked_string_body_with_annotation :
      STRING_EXCEPT_SPECIAL
        {
            char *s = escape($1);
            printf("<mark>%s</mark>", s); free(s); free($1);
        }
    | STRING_EXCEPT_SPECIAL START_ANNOTATION string_except_parenthesis
        {
            char *s1 = escape($1), *s3 = escape($3);
            printf("<mark>%s<annotation>%s</annotation></mark>", s1, s3);
            free(s1); free(s3); free($1); free($2); free($3);
        }
    ;
...snip
%%
...snip

static char *escape(const char *s)
{
    const char *p = s;
    char *q;
    size_t len = 1;
    while (*p != '\0') {
        switch (*p++) {
        case '<': case '>':
            len += 4; break;
        case '&':
            len += 5; break;
        case '\'': case '"':
            len += 6; break;
        default:
            len++; break;
        }
    }
    q = malloc(len);
    while (*s != '\0') {
        switch (*s) {
        case '<':
            *q++ = '&', *q++ = 'l', *q++ = 't', *q++ = ';'; break;
        case '>':
            *q++ = '&', *q++ = 'g', *q++ = 't', *q++ = ';'; break;
        case '&':
            *q++ = '&', *q++ = 'a', *q++ = 'm', *q++ = 'p', *q++ = ';'; break;
        case '\'':
            *q++ = '&', *q++ = 'a', *q++ = 'p', *q++ = 'o', *q++ = 's', *q++ = ';'; break;
        case '"':
            *q++ = '&', *q++ = 'q', *q++ = 'u', *q++ = 'o', *q++ = 't', *q++ = ';'; break;
        default:
            *q++ = *s; break;
        }
        s++;
    }
    *q = '\0';
    return q - len + 1;
}

文字列中の'<''>''&''\'''"'をエスケープする関数escapeを定義し、
二つ目の%%の後の追加のCコード部の末尾に追加した。
構文規則部のアクションで入力から得た文字列を出力する場合は、この関数を通した文字列を出力するようにする。
修正前のコードでは、

$ ./translator << -----
> XML provides five predefined entities: (<,>,&,',").
> -----
<text>XML provides five predefined entities: <mark><,>,&,',"</mark>.
</text>

のように正しくマーク付けされた入力だがXMLとして誤った形式で出力される。
修正後は、

$ ./translator << -----
> XML provides five predefined entities: (<,>,&,',").
> -----
<text>XML provides five predefined entities: <mark>&lt;,&gt;,&amp;,&apos;,&quot;</mark>.
</text>


と、正しい形式で出力される。