(root)/
gettext-0.22.4/
libtextstyle/
lib/
html-ostream.oo.c
       1  /* Output stream that produces HTML output.
       2     Copyright (C) 2006-2009, 2019-2020 Free Software Foundation, Inc.
       3     Written by Bruno Haible <bruno@clisp.org>, 2006.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation; either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <config.h>
      19  
      20  /* Specification.  */
      21  #include "html-ostream.h"
      22  
      23  #include <stdio.h>
      24  #include <stdlib.h>
      25  #include <string.h>
      26  
      27  #include "gl_xlist.h"
      28  #include "gl_array_list.h"
      29  #include "minmax.h"
      30  #include "unistr.h"
      31  #include "xalloc.h"
      32  
      33  struct html_ostream : struct ostream
      34  {
      35  fields:
      36    /* The destination stream.  */
      37    ostream_t destination;
      38    /* The current hyperlink ref.  */
      39    char *hyperlink_ref;
      40    /* The stack of active CSS classes.  */
      41    gl_list_t /* <char *> */ class_stack;
      42    /* Current and last size of the active portion of this stack.  Always
      43       size(class_stack) == max(curr_class_stack_size,last_class_stack_size).  */
      44    size_t curr_class_stack_size;
      45    size_t last_class_stack_size;
      46    /* Last few bytes that could not yet be converted.  */
      47    #define BUFSIZE 6
      48    char buf[BUFSIZE];
      49    size_t buflen;
      50  };
      51  
      52  /* Emit an HTML attribute value.
      53     quote is either '"' or '\''.  */
      54  static void
      55  write_attribute_value (html_ostream_t stream, const char *value, char quote)
      56  {
      57    /* Need to escape the '<', '>', '&', quote characters.  */
      58    ostream_t destination = stream->destination;
      59    const char *p = value;
      60  
      61    for (;;)
      62      {
      63        const char *q = p;
      64  
      65        while (*q != '\0' && *q != '<' && *q != '>' && *q != '&' && *q != quote)
      66          q++;
      67        if (p < q)
      68          ostream_write_mem (destination, p, q - p);
      69        if (*q == '\0')
      70          break;
      71        switch (*q)
      72          {
      73          case '<':
      74            ostream_write_str (destination, "&lt;");
      75            break;
      76          case '>':
      77            ostream_write_str (destination, "&gt;");
      78            break;
      79          case '&':
      80            ostream_write_str (destination, "&amp;");
      81            break;
      82          case '"':
      83            ostream_write_str (destination, "&quot;");
      84            break;
      85          case '\'':
      86            ostream_write_str (destination, "&apos;");
      87            break;
      88          default:
      89            abort ();
      90          }
      91        p = q + 1;
      92      }
      93  }
      94  
      95  /* Implementation of ostream_t methods.  */
      96  
      97  static void
      98  verify_invariants (html_ostream_t stream)
      99  {
     100    /* Verify the invariant regarding size(class_stack).  */
     101    if (gl_list_size (stream->class_stack)
     102        != MAX (stream->curr_class_stack_size, stream->last_class_stack_size))
     103      abort ();
     104  }
     105  
     106  /* Removes the excess elements of class_stack.
     107     Needs to be called after max(curr_class_stack_size,last_class_stack_size)
     108     may have been reduced.  */
     109  static void
     110  shrink_class_stack (html_ostream_t stream)
     111  {
     112    size_t keep =
     113      MAX (stream->curr_class_stack_size, stream->last_class_stack_size);
     114    size_t i = gl_list_size (stream->class_stack);
     115    while (i > keep)
     116      {
     117        i--;
     118        free ((char *) gl_list_get_at (stream->class_stack, i));
     119        gl_list_remove_at (stream->class_stack, i);
     120      }
     121  }
     122  
     123  /* Emits <span> or </span> tags, to follow the increase / decrease of the
     124     class_stack from last_class_stack_size to curr_class_stack_size.
     125     When done, sets last_class_stack_size to curr_class_stack_size.  */
     126  static void
     127  emit_pending_spans (html_ostream_t stream, bool shrink_stack)
     128  {
     129    if (stream->curr_class_stack_size > stream->last_class_stack_size)
     130      {
     131        size_t i;
     132  
     133        for (i = stream->last_class_stack_size; i < stream->curr_class_stack_size; i++)
     134          {
     135            char *classname = (char *) gl_list_get_at (stream->class_stack, i);
     136  
     137            ostream_write_str (stream->destination, "<span class=\"");
     138            ostream_write_str (stream->destination, classname);
     139            ostream_write_str (stream->destination, "\">");
     140          }
     141        stream->last_class_stack_size = stream->curr_class_stack_size;
     142      }
     143    else if (stream->curr_class_stack_size < stream->last_class_stack_size)
     144      {
     145        size_t i;
     146  
     147        for (i = stream->last_class_stack_size; i > stream->curr_class_stack_size; i--)
     148          ostream_write_str (stream->destination, "</span>");
     149        stream->last_class_stack_size = stream->curr_class_stack_size;
     150        if (shrink_stack)
     151          shrink_class_stack (stream);
     152      }
     153    /* Here last_class_stack_size == curr_class_stack_size.  */
     154    if (shrink_stack)
     155      verify_invariants (stream);
     156  }
     157  
     158  static void
     159  html_ostream::write_mem (html_ostream_t stream, const void *data, size_t len)
     160  {
     161    if (len > 0)
     162      {
     163        #define BUFFERSIZE 2048
     164        char inbuffer[BUFFERSIZE];
     165        size_t inbufcount;
     166  
     167        inbufcount = stream->buflen;
     168        if (inbufcount > 0)
     169          memcpy (inbuffer, stream->buf, inbufcount);
     170        for (;;)
     171          {
     172            /* At this point, inbuffer[0..inbufcount-1] is filled.  */
     173            {
     174              /* Combine the previous rest with a chunk of new input.  */
     175              size_t n =
     176                (len <= BUFFERSIZE - inbufcount ? len : BUFFERSIZE - inbufcount);
     177  
     178              if (n > 0)
     179                {
     180                  memcpy (inbuffer + inbufcount, data, n);
     181                  data = (const char *) data + n;
     182                  inbufcount += n;
     183                  len -= n;
     184                }
     185            }
     186            {
     187              /* Handle complete UTF-8 characters.  */
     188              const char *inptr = inbuffer;
     189              size_t insize = inbufcount;
     190  
     191              while (insize > 0)
     192                {
     193                  unsigned char c0;
     194                  ucs4_t uc;
     195                  int nbytes;
     196  
     197                  c0 = ((const unsigned char *) inptr)[0];
     198                  if (insize < (c0 < 0xc0 ? 1 : c0 < 0xe0 ? 2 : c0 < 0xf0 ? 3 :
     199                                c0 < 0xf8 ? 4 : c0 < 0xfc ? 5 : 6))
     200                    break;
     201  
     202                  nbytes = u8_mbtouc (&uc, (const unsigned char *) inptr, insize);
     203  
     204                  if (uc == '\n')
     205                    {
     206                      verify_invariants (stream);
     207                      /* Emit </span> tags to follow the decrease of the class stack
     208                         from last_class_stack_size to 0.  Then emit the newline.
     209                         Then prepare for emitting <span> tags to go back from 0
     210                         to curr_class_stack_size.  */
     211                      size_t prev_class_stack_size = stream->curr_class_stack_size;
     212                      stream->curr_class_stack_size = 0;
     213                      emit_pending_spans (stream, false);
     214                      stream->curr_class_stack_size = prev_class_stack_size;
     215                      ostream_write_str (stream->destination, "<br/>");
     216                      shrink_class_stack (stream);
     217                      verify_invariants (stream);
     218                    }
     219                  else
     220                    {
     221                      emit_pending_spans (stream, true);
     222  
     223                      switch (uc)
     224                        {
     225                        case '"':
     226                          ostream_write_str (stream->destination, "&quot;");
     227                          break;
     228                        case '&':
     229                          ostream_write_str (stream->destination, "&amp;");
     230                          break;
     231                        case '<':
     232                          ostream_write_str (stream->destination, "&lt;");
     233                          break;
     234                        case '>':
     235                          /* Needed to avoid "]]>" in the output.  */
     236                          ostream_write_str (stream->destination, "&gt;");
     237                          break;
     238                        case ' ':
     239                          /* Needed because HTML viewers merge adjacent spaces
     240                             and drop spaces adjacent to <br> and similar.  */
     241                          ostream_write_str (stream->destination, "&nbsp;");
     242                          break;
     243                        default:
     244                          if (uc >= 0x20 && uc < 0x7F)
     245                            {
     246                              /* Output ASCII characters as such.  */
     247                              char bytes[1];
     248                              bytes[0] = uc;
     249                              ostream_write_mem (stream->destination, bytes, 1);
     250                            }
     251                          else
     252                            {
     253                              /* Output non-ASCII characters in #&nnn;
     254                                 notation.  */
     255                              char bytes[32];
     256                              sprintf (bytes, "&#%d;", (int) uc);
     257                              ostream_write_str (stream->destination, bytes);
     258                            }
     259                          break;
     260                        }
     261                    }
     262  
     263                  inptr += nbytes;
     264                  insize -= nbytes;
     265                }
     266              /* Put back the unconverted part.  */
     267              if (insize > BUFSIZE)
     268                abort ();
     269              if (len == 0)
     270                {
     271                  if (insize > 0)
     272                    memcpy (stream->buf, inptr, insize);
     273                  stream->buflen = insize;
     274                  break;
     275                }
     276              if (insize > 0)
     277                memmove (inbuffer, inptr, insize);
     278              inbufcount = insize;
     279            }
     280          }
     281        #undef BUFFERSIZE
     282      }
     283  }
     284  
     285  static void
     286  html_ostream::flush (html_ostream_t stream, ostream_flush_scope_t scope)
     287  {
     288    verify_invariants (stream);
     289    /* stream->buf[] contains only a few bytes that don't correspond to a
     290       character.  Can't flush it.  */
     291    /* Close the open <span> tags, and prepare for reopening the same <span>
     292       tags.  */
     293    size_t prev_class_stack_size = stream->curr_class_stack_size;
     294    stream->curr_class_stack_size = 0;
     295    emit_pending_spans (stream, false);
     296    stream->curr_class_stack_size = prev_class_stack_size;
     297    shrink_class_stack (stream);
     298    verify_invariants (stream);
     299  
     300    if (scope != FLUSH_THIS_STREAM)
     301      ostream_flush (stream->destination, scope);
     302  }
     303  
     304  static void
     305  html_ostream::free (html_ostream_t stream)
     306  {
     307    stream->curr_class_stack_size = 0;
     308    emit_pending_spans (stream, true);
     309    if (stream->hyperlink_ref != NULL)
     310      {
     311        /* Close the current <a> element.  */
     312        ostream_write_str (stream->destination, "</a>");
     313        free (stream->hyperlink_ref);
     314      }
     315    verify_invariants (stream);
     316    gl_list_free (stream->class_stack);
     317    free (stream);
     318  }
     319  
     320  /* Implementation of html_ostream_t methods.  */
     321  
     322  static void
     323  html_ostream::begin_span (html_ostream_t stream, const char *classname)
     324  {
     325    verify_invariants (stream);
     326    if (stream->last_class_stack_size > stream->curr_class_stack_size
     327        && strcmp ((char *) gl_list_get_at (stream->class_stack,
     328                                            stream->curr_class_stack_size),
     329                   classname) != 0)
     330      emit_pending_spans (stream, true);
     331    /* Now either
     332         last_class_stack_size <= curr_class_stack_size
     333         - in this case we have to append the given CLASSNAME -
     334       or
     335         last_class_stack_size > curr_class_stack_size
     336         && class_stack[curr_class_stack_size] == CLASSNAME
     337         - in this case we only need to increment curr_class_stack_size.  */
     338    if (stream->last_class_stack_size <= stream->curr_class_stack_size)
     339      gl_list_add_at (stream->class_stack, stream->curr_class_stack_size,
     340                      xstrdup (classname));
     341    stream->curr_class_stack_size++;
     342    verify_invariants (stream);
     343  }
     344  
     345  static void
     346  html_ostream::end_span (html_ostream_t stream, const char *classname)
     347  {
     348    verify_invariants (stream);
     349    if (stream->curr_class_stack_size > 0)
     350      {
     351        char *innermost_active_span =
     352          (char *) gl_list_get_at (stream->class_stack,
     353                                   stream->curr_class_stack_size - 1);
     354        if (strcmp (innermost_active_span, classname) == 0)
     355          {
     356            stream->curr_class_stack_size--;
     357            shrink_class_stack (stream);
     358            verify_invariants (stream);
     359            return;
     360          }
     361      }
     362    /* Improperly nested begin_span/end_span calls.  */
     363    abort ();
     364  }
     365  
     366  static const char *
     367  html_ostream::get_hyperlink_ref (html_ostream_t stream)
     368  {
     369    return stream->hyperlink_ref;
     370  }
     371  
     372  static void
     373  html_ostream::set_hyperlink_ref (html_ostream_t stream, const char *ref)
     374  {
     375    char *ref_copy = (ref != NULL ? xstrdup (ref) : NULL);
     376  
     377    verify_invariants (stream);
     378    if (stream->hyperlink_ref != NULL)
     379      {
     380        /* Close the open <span> tags, and prepare for reopening the same <span>
     381           tags.  */
     382        size_t prev_class_stack_size = stream->curr_class_stack_size;
     383        stream->curr_class_stack_size = 0;
     384        emit_pending_spans (stream, false);
     385        stream->curr_class_stack_size = prev_class_stack_size;
     386        /* Close the current <a> element.  */
     387        ostream_write_str (stream->destination, "</a>");
     388        shrink_class_stack (stream);
     389  
     390        free (stream->hyperlink_ref);
     391      }
     392    stream->hyperlink_ref = ref_copy;
     393    if (stream->hyperlink_ref != NULL)
     394      {
     395        /* Close the open <span> tags, and prepare for reopening the same <span>
     396           tags.  */
     397        size_t prev_class_stack_size = stream->curr_class_stack_size;
     398        stream->curr_class_stack_size = 0;
     399        emit_pending_spans (stream, false);
     400        stream->curr_class_stack_size = prev_class_stack_size;
     401        /* Open an <a> element.  */
     402        ostream_write_str (stream->destination, "<a href=\"");
     403        write_attribute_value (stream, stream->hyperlink_ref, '"');
     404        ostream_write_str (stream->destination, "\">");
     405        shrink_class_stack (stream);
     406      }
     407    verify_invariants (stream);
     408  }
     409  
     410  static void
     411  html_ostream::flush_to_current_style (html_ostream_t stream)
     412  {
     413    verify_invariants (stream);
     414    /* stream->buf[] contains only a few bytes that don't correspond to a
     415       character.  Can't flush it.  */
     416    /* Open all requested <span> tags.  */
     417    emit_pending_spans (stream, true);
     418    verify_invariants (stream);
     419  }
     420  
     421  /* Constructor.  */
     422  
     423  html_ostream_t
     424  html_ostream_create (ostream_t destination)
     425  {
     426    html_ostream_t stream = XMALLOC (struct html_ostream_representation);
     427  
     428    stream->base.vtable = &html_ostream_vtable;
     429    stream->destination = destination;
     430    stream->hyperlink_ref = NULL;
     431    stream->class_stack =
     432      gl_list_create_empty (GL_ARRAY_LIST, NULL, NULL, NULL, true);
     433    stream->curr_class_stack_size = 0;
     434    stream->last_class_stack_size = 0;
     435    stream->buflen = 0;
     436  
     437    return stream;
     438  }
     439  
     440  /* Accessors.  */
     441  
     442  static ostream_t
     443  html_ostream::get_destination (html_ostream_t stream)
     444  {
     445    return stream->destination;
     446  }
     447  
     448  /* Instanceof test.  */
     449  
     450  bool
     451  is_instance_of_html_ostream (ostream_t stream)
     452  {
     453    return IS_INSTANCE (stream, ostream, html_ostream);
     454  }