/* lineout.c -
   Implements line-oriented output format.

     Written by James Clark (jjc@jclark.com).
*/

#include "config.h"
#include "std.h"
#include "entity.h"           /* Templates for entity control blocks. */
#include "adl.h"              /* Definitions for attribute list processing. */
#include "sgmlmain.h"         /* Main interface to SGML services. */
#include "lineout.h"
#include "appl.h"

static VOID flush_data P((void));
static VOID define_external_entity P((PNE));
static VOID define_entity P((UNCH *));
static VOID handle_attributes P((UNCH *, struct ad *));
static VOID handle_token_list P((UNCH *, struct ad *, int));
static VOID handle_single_token P((UNCH *, struct ad *, int));
static VOID output_notation P((UNCH *, UNCH *, UNCH *));
static VOID output_internal_entity P((UNCH *, int, UNCH *));
static VOID output_external_entity P((UNCH *, int, UNIV, UNCH *, UNCH *,
				      UNCH *));
static VOID output_subdoc P((UNCH *, UNIV, UNCH *, UNCH *));
#ifdef SUPPORT_SUBDOC
static VOID process_subdoc P((UNCH *, UNIV));
#endif /* SUPPORT_SUBDOC */
static VOID output_record_end P((void));
static VOID output_pcdata P((UNS, UNCH *));
static VOID output_cdata P((UNS, UNCH *));
static VOID output_sdata P((UNS, UNCH *));
static VOID output_entity_reference P((UNCH *));
static VOID output_start_tag P((UNCH *));
static VOID output_end_tag P((UNCH *));
static VOID output_processing_instruction P((UNS, UNCH *));
static VOID output_implied_attribute P((UNCH *, UNCH *));
static char *attribute_type_string P((int));
static VOID output_begin_attribute P((UNCH *, UNCH *, int));
static VOID output_attribute_token P((UNS, UNCH *));
static VOID output_end_attribute P((void));
static VOID print_data P((UNS, UNCH *, int));
static VOID print_string P((UNS, UNCH *, int));
static VOID print_id P((UNIV, UNCH *, UNCH *));
static VOID print_filename P((char *));
static VOID output_location P((void));
static VOID output_appinfo P((UNS, UNCH *));

static int have_data = 0;
static char *current_filename = 0;
static unsigned long current_lineno = 0;

VOID process_document(subdocsw)
int subdocsw;
{
     enum sgmlevent rc;
     struct rcbtag rcbtag;
     struct rcbdata rcbdaf;

     while ((rc = sgmlnext(&rcbdaf, &rcbtag)) != SGMLEOD) {
#ifdef SUPPORT_SUBDOC
	  if (rc == SGMLDAF && !CONTERSW(rcbdaf) && NDESW(rcbdaf)
	      && NEXTYPE(NEPTR(rcbdaf)) == ESNSUB) {
	       if (!suppsw && !sgmlment(NEENAME(NEPTR(rcbdaf))))
		    define_external_entity(NEPTR(rcbdaf));
	       process_subdoc(NEENAME(NEPTR(rcbdaf)) + 1,
			      NEID(NEPTR(rcbdaf)));
	       continue;
	  }
#endif /* SUPPORT_SUBDOC */
	  if (!suppsw)
	       switch (rc) {
	       case SGMLDAF:
		    if (CONTERSW(rcbdaf))
			 break;
		    if (CDESW(rcbdaf))
			 output_cdata(CDATALEN(rcbdaf), CDATA(rcbdaf));
		    else if (SDESW(rcbdaf))
			 output_sdata(CDATALEN(rcbdaf), CDATA(rcbdaf));
		    else if (NDESW(rcbdaf)) {
			 assert(NEXTYPE(NEPTR(rcbdaf)) != ESNSUB);
			 if (!sgmlment(NEENAME(NEPTR(rcbdaf))))
			      define_external_entity(NEPTR(rcbdaf));
			 output_entity_reference(NEENAME(NEPTR(rcbdaf)) + 1);
		    }
		    else
			 output_pcdata(CDATALEN(rcbdaf), CDATA(rcbdaf));
		    break;
	       case SGMLSTG:
		    if (CONTERSW(rcbtag))
			 break;
		    if (ALPTR(rcbtag))
			 handle_attributes((UNCH *)NULL, ALPTR(rcbtag));
		    output_start_tag(CURGI(rcbtag));
		    break;
	       case SGMLETG:
		    if (CONTERSW(rcbtag))
			 break;
		    output_end_tag(CURGI(rcbtag));
		    break;
	       case SGMLPIS:
		    if (CONTERSW(rcbdaf))
			 break;
		    output_processing_instruction(PDATALEN(rcbdaf),
						  PDATA(rcbdaf));
		    break;
	       case SGMLREF:
		    if (CONTERSW(rcbdaf))
			 break;
		    output_record_end();
		    break;
	       case SGMLAPP:
		    if (CONTERSW(rcbdaf))
			 break;
		    if (!subdocsw)
			 output_appinfo(ADATALEN(rcbdaf), ADATA(rcbdaf));
		    break;
	       default:
		    abort();
	       }
     }
}

/* Output an indication that the document was conforming. */

VOID output_conforming()
{
     if (!suppsw)
	  printf("%c\n", CONFORMING_CODE);
}

static VOID define_external_entity(p)
PNE p;
{
     if (NEXTYPE(p) == ESNSUB)
	  output_subdoc(NEENAME(p) + 1, NEID(p), NEPUBID(p), NESYSID(p));
     else {
	  if (!NEDCNMARK(p))
	       output_notation(NEDCN(p) + 1, NEDCNPUBID(p), NEDCNSYSID(p));
	  output_external_entity(NEENAME(p) + 1, NEXTYPE(p), NEID(p),
				 NEPUBID(p), NESYSID(p), NEDCN(p) + 1);
	  if (NEAL(p))
	       handle_attributes(NEENAME(p) + 1, NEAL(p));
     }
}

static VOID define_entity(ename)
UNCH *ename;
{
     int rc;
     PNE np;
     UNCH *tp;
     
     if (sgmlment(ename))		/* already defined it */
	  return;
     rc = sgmlgent(ename, &np, &tp);
     switch (rc) {
     case 1:
	  define_external_entity(np);
	  break;
     case 2:
     case 3:
	  output_internal_entity(ename + 1, rc == 3, tp);
	  break;
     }
}

/* ENT is the name of the entity with which these attributes are associated;
if it's NULL, they're associated with the next start tag. */

static VOID handle_attributes(ent, al)
UNCH *ent;
struct ad *al;
{
     int aln;

     for (aln = 1; aln <= ADN(al); aln++) {
	  if (GET(ADFLAGS(al, aln), AERROR))
	       ;
	  else if (GET(ADFLAGS(al, aln), AINVALID))
	       ;
	  else if (ADVAL(al, aln) == NULL)
	       output_implied_attribute(ent, ADNAME(al, aln));
	  else if (ADTYPE(al, aln) >= ATKNLIST)
	       handle_token_list(ent, al, aln);
	  else
	       handle_single_token(ent, al, aln);
	  if (BITON(ADFLAGS(al, aln), AGROUP))
	       aln += ADNUM(al, aln);
     }
}

static VOID handle_token_list(ent, al, aln)
UNCH *ent;
struct ad *al;
int aln;
{
     UNCH *ptr;
     int i;
     if (ADTYPE(al, aln) == AENTITYS) {
	  ptr = ADVAL(al, aln);
	  for (i = 0; i < ADNUM(al, aln); i++) {
	       /* Temporarily make token look like normal
		  name with length and EOS. */
	       UNCH c = ptr[*ptr + 1];
	       ptr[*ptr + 1] = '\0';
	       *ptr += 2;
	       define_entity(ptr);
	       *ptr -= 2;
	       ptr += *ptr + 1;
	       *ptr = c;
	  }
     }
     output_begin_attribute(ent, ADNAME(al, aln), ADTYPE(al, aln));
     ptr = ADVAL(al, aln);
     for (i = 0; i < ADNUM(al, aln); i++) {
	  /* The first byte is a length NOT including the length
	     byte; the tokens are not EOS terminated. */
	  output_attribute_token(*ptr, ptr + 1);
	  ptr += *ptr + 1;
     }
     output_end_attribute();
}

static VOID handle_single_token(ent, al, aln)
UNCH *ent;
struct ad *al;
int aln;
{
     if (ADTYPE(al, aln) == ANOTEGRP && !DCNMARK(ADDATA(al, aln).x))
	  output_notation(ADVAL(al, aln) + 1,
			  ADDATA(al, aln).x->pubid,
			  ADDATA(al, aln).x->sysid);
     else if (ADTYPE(al, aln) == AENTITY)
	  define_entity(ADVAL(al, aln));
     output_begin_attribute(ent, ADNAME(al, aln), ADTYPE(al, aln));
     if (ADTYPE(al, aln) == ACHARS) {
	  putchar(' ');
	  print_string(ustrlen(ADVAL(al, aln)), ADVAL(al, aln), 0);
     }
     else
	  output_attribute_token(*ADVAL(al, aln) - 2, ADVAL(al, aln) + 1);
     output_end_attribute();
}

static VOID output_notation(name, pubid, sysid)
UNCH *name;
UNCH *pubid, *sysid;
{
     flush_data();
     print_id((UNIV)0, pubid, sysid);
     printf("%c%s\n", DEFINE_NOTATION_CODE, name);
}

static VOID output_internal_entity(ename, is_sdata, text)
UNCH *ename;
int is_sdata;
UNCH *text;
{
     flush_data();
     printf("%c%s %s ", DEFINE_INTERNAL_ENTITY_CODE, ename,
	    is_sdata ? "SDATA" : "CDATA");
     print_string(text ? ustrlen(text) : 0, text, 0);
     putchar('\n');
}

static VOID output_subdoc(nm, id, pubid, sysid)
UNCH *nm;
UNIV id;
UNCH *pubid, *sysid;
{
     flush_data();
     print_id(id, pubid, sysid);
     printf("%c%s\n", DEFINE_SUBDOC_ENTITY_CODE, nm);
}

#ifdef SUPPORT_SUBDOC

static VOID process_subdoc(nm, id)
UNCH *nm;
UNIV id;
{
     if (!suppsw) {
	  flush_data();
	  output_location();
	  printf("%c%s\n", START_SUBDOC_CODE, nm);
	  fflush(stdout);
     }
     fflush(stderr);

     if (id) {
	  char **argv;
	  int ret;

	  argv = make_argv(id);
	  ret = run_process(argv);
	  if (ret != 0)
	       suberr++;
	  
	  current_filename = 0;
	  free(argv);
	  if (ret == 0)
	       get_subcaps();
     }
     else {
	  suberr++;
	  appl_error(E_SUBDOC, nm);
     }

     if (!suppsw)
	  printf("%c%s\n", END_SUBDOC_CODE, nm);
}

#endif /* SUPPORT_SUBDOC */

static VOID output_external_entity(nm, xtype, id, pubid, sysid, dcn)
UNCH *nm, *dcn;
UNIV id;
UNCH *pubid, *sysid;
int xtype;
{
     char *type;

     flush_data();

     print_id(id, pubid, sysid);

     switch (xtype) {
     case ESNCDATA:
	  type = "CDATA";
	  break;
     case ESNNDATA:
	  type = "NDATA";
	  break;
     case ESNSDATA:
	  type = "SDATA";
	  break;
     default:
	  return;
     }
     printf("%c%s %s %s\n", DEFINE_EXTERNAL_ENTITY_CODE, nm, type, dcn);
}

static VOID output_record_end()
{
     static UNCH re = RECHAR;
     print_data(1, &re, 0);
}

static VOID output_pcdata(n, s)
UNS n;
UNCH *s;
{
     print_data(n, s, 0);
}

static VOID output_cdata(n, s)
UNS n;
UNCH *s;
{
     print_data(n, s, 0);
}

static VOID output_sdata(n, s)
UNS n;
UNCH *s;
{
     print_data(n, s, 1);
}

static VOID output_entity_reference(s)
UNCH *s;
{
     flush_data();
     output_location();
     printf("%c%s\n", REFERENCE_ENTITY_CODE, s);
}

static VOID output_start_tag(s)
UNCH *s;
{
     flush_data();
     output_location();
     printf("%c%s\n", START_CODE, s);
}

static VOID output_end_tag(s)
UNCH *s;
{
     flush_data();
     printf("%c%s\n", END_CODE, s);
}

static VOID output_processing_instruction(n, s)
UNS n;
UNCH *s;
{
     flush_data();
     output_location();
     putchar(PI_CODE);
     print_string(n, s, 0);
     putchar('\n');
}

static VOID output_appinfo(n, s)
UNS n;
UNCH *s;
{
     flush_data();
     output_location();
     putchar(APPINFO_CODE);
     print_string(n, s, 0);
     putchar('\n');
}
     

static VOID output_implied_attribute(ent, aname)
UNCH *ent, *aname;
{
     flush_data();
     if (ent)
	  printf("%c%s %s IMPLIED\n", DATA_ATTRIBUTE_CODE, ent, aname);
     else
	  printf("%c%s IMPLIED\n", ATTRIBUTE_CODE, aname);
}

static char *attribute_type_string(type)
int type;
{
     switch (type) {
     case ANMTGRP:
     case ANAME:
     case ANMTOKE:
     case ANUTOKE:
     case ANUMBER:
     case ANAMES:
     case ANMTOKES:
     case ANUTOKES:
     case ANUMBERS:
     case AID:
     case AIDREF:
     case AIDREFS:
	  return "TOKEN";
     case ANOTEGRP:
	  return "NOTATION";
     case ACHARS:
	  return "CDATA";
     case AENTITY:
     case AENTITYS:
	  return "ENTITY";
     }
#if 0
     fatal("invalid attribute type %d", type);
#endif
     return "INVALID";
}	  

static VOID output_begin_attribute(ent, aname, type)
UNCH *ent, *aname;
int type;
{
     flush_data();
     if (ent)
	  printf("%c%s %s %s", DATA_ATTRIBUTE_CODE, ent, aname,
		 attribute_type_string(type));
     else
	  printf("%c%s %s", ATTRIBUTE_CODE, aname,
		 attribute_type_string(type));

}

static VOID output_attribute_token(vallen, val)
UNS vallen;
UNCH *val;
{
     putchar(' ');
     for (; vallen > 0; --vallen, ++val)
	  putchar(*val);
}

static VOID output_end_attribute()
{
     putchar('\n');
}

static VOID print_data(n, s, is_sdata)
UNS n;
UNCH *s;
int is_sdata;
{
     if (n > 0 || is_sdata) {
	  if (n == 1 && *s == RECHAR)
	       current_lineno++;
	  else
	       output_location();
	  if (!have_data)
	       putchar(DATA_CODE);
	  print_string(n, s, is_sdata);
	  have_data = 1;
     }
}

static VOID flush_data()
{
     if (have_data) {
	  putchar('\n');
	  have_data = 0;
     }
}

static VOID output_location()
{
     char *filename;
     unsigned long lineno;
     int filename_changed = 0;

     if (!locsw)
	  return;
     if (!sgmlloc(&lineno, &filename))
	  return;
     if (!current_filename || strcmp(filename, current_filename) != 0)
	  filename_changed = 1;
     else if (lineno == current_lineno)
	  return;
     flush_data();
     printf("%c%lu", LOCATION_CODE, lineno);
     current_lineno = lineno;
     if (filename_changed) {
	  putchar(' ');
	  print_filename(filename);
	  current_filename = filename;
     }
     putchar('\n');
}

static VOID print_string(slen, s, is_sdata)
UNS slen;
UNCH *s;
int is_sdata;
{
     if (is_sdata)
	  fputs("\\|", stdout);
     while (slen > 0) {
	  UNCH ch = *s++;
	  slen--;
	  if (ch == DELSDATA) {
	       if (is_sdata)
		    ;		/* I don't think this should happen */
	       else
		    fputs("\\|", stdout);
	       ;
	  }
	  else if (ch == DELCDATA)
	       ;
	  else {
	       if (ch == DELNONCH) {
		    if (!slen)
			 break;
		    ch = UNSHIFTNON(*s);
		    s++;
		    slen--;
	       }
	       switch (ch) {
	       case RECHAR:
		    fputs("\\n", stdout);
		    break;
	       case '\\':
		    fputs("\\\\", stdout);
		    break;
	       default:
		    if (ISASCII(ch) && isprint(ch))
			 putchar(ch);
		    else
			 printf("\\%03o", ch);
		    break;
	       }
	  }
     }
     if (is_sdata)
	  fputs("\\|", stdout);
}
		    

static VOID print_id(id, pubid, sysid)
UNIV id;
UNCH *pubid;
UNCH *sysid;
{

     if (pubid) {
	  putchar(PUBID_CODE);
	  print_string(ustrlen(pubid), pubid, 0);
	  putchar('\n');
     }

     if (sysid) {
	  putchar(SYSID_CODE);
	  print_string(ustrlen(sysid), sysid, 0);
	  putchar('\n');
     }

     if (id) {
	  char *p;

	  for (p = id; *p != '\0'; p++) {
	       putchar(FILE_CODE);
	       do {
		    switch (*p) {
		    case '\\':
			 fputs("\\\\", stdout);
			 break;
		    case '\n':
			 fputs("\\n", stdout);
			 break;
		    default:
			 if (ISASCII(*p) && isprint((UNCH)*p))
			      putchar(*p);
			 else
			      printf("\\%03o", (UNCH)*p);
			 break;
		    }
	       } while (*++p);
	       putchar('\n');
	  }
     }
}

static VOID print_filename(s)
char *s;
{
     for (; *s; s++)
	  switch (*s) {
	  case '\\':
	       fputs("\\\\", stdout);
	       break;
	  case '\n':
	       fputs("\\n", stdout);
	       break;
	  default:
	       if (ISASCII(*s) && isprint((UNCH)*s))
		    putchar(*s);
	       else
		    printf("\\%03o", (UNCH)*s);
	       break;
	  }
}

/*
Local Variables:
c-indent-level: 5
c-continued-statement-offset: 5
c-brace-offset: -5
c-argdecl-indent: 0
c-label-offset: -5
End:
*/
