package com.dbxml.xml.text;

/*
 * dbXML - Native XML Database
 * Copyright (C) 1999-2004  The dbXML Group, L.L.C.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: TextProducer.java,v 1.7 2004/02/12 00:17:58 bradford Exp $
 */

import com.dbxml.xml.dtsm.*;

import com.dbxml.util.UTF8;
import com.dbxml.xml.QName;
import com.dbxml.xml.SymbolTable;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;

/**
 * TextProducer
 */

public final class TextProducer extends Producer implements Consumer {
   private PrintWriter writer;
   private boolean omitPrefix;

   public TextProducer(DocumentTable table, boolean omitPrefix) {
      this(table);
      this.omitPrefix = omitPrefix;
   }

   public TextProducer(DocumentTable table) {
      setDocumentTable(table);
   }

   public static String toString(DocumentTable table) throws Exception {
      TextProducer p = new TextProducer(table);
      int size = (table.getEntryCount() * Constants.DOC_ENTRY_SIZE)
         + (table.getValueCount() * Constants.VAL_ENTRY_SIZE)
         + (table.getValueCount() * 64);
      StringWriter w = new StringWriter(size);
      p.setWriter(w);
      p.process();
      return w.getBuffer().toString();
   }

   public static void toWriter(DocumentTable table, Writer writer) throws Exception {
      TextProducer p = new TextProducer(table);
      p.setWriter(writer);
      p.process();
      writer.flush();
   }

   public void setWriter(PrintWriter writer) {
      this.writer = writer;
   }

   public void setWriter(Writer writer) {
      this.writer = new PrintWriter(writer);
   }

   public void beginDocument(DocumentTableEntry entry) throws Exception {
      if ( !omitPrefix )
         writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
   }

   public void endDocument(DocumentTableEntry entry) throws Exception {
   }

   public void beginElement(DocumentTableEntry entry) throws Exception {
      int symID = entry.getValID();
      QName qname = symbols.getQName(symID);
      writer.print('<');
      writer.print(qname.getName());
      while ( processor.peek().getTypeID() == Constants.OBJ_BEGIN_ATTRIBUTE )
         attribute(processor.next());
      if ( processor.peek().getTypeID() == Constants.OBJ_END_ELEMENT ) {
         processor.next();
         writer.print("/>");
      }
      else
         writer.print('>');
   }

   public void endElement(DocumentTableEntry entry) throws Exception {
      int symID = entry.getValID();
      QName qname = symbols.getQName(symID);
      writer.print("</");
      writer.print(qname.getName());
      writer.print('>');
   }

   public void attribute(DocumentTableEntry entry) throws Exception {
      int symID = entry.getValID();
      QName qname = symbols.getQName(symID);
      String uri = qname.getURI();
      writer.print(' ');
      writer.print(qname.getName());
      writer.print("=\"");
      do {
         entry = processor.next();
         if ( entry.getTypeID() == Constants.OBJ_TEXT ) {
            int valID = entry.getValID();
            writeEscapedText(UTF8.toCharArray(table.getValue(valID)));
         }
      }
      while ( entry.getTypeID() != Constants.OBJ_END_ATTRIBUTE );
      writer.print('\"');
   }

   public void procInst(DocumentTableEntry entry) throws Exception {
      int symID = entry.getValID();
      QName qname = symbols.getQName(symID);
      entry = processor.next();
      int valID = entry.getValID();
      processor.next(); // Eat the OBJ_END_PROCINST
      writer.print("<?");
      writer.print(qname.getName());
      writer.print(' ');
      UTF8.toWriter(table.getValue(valID), writer);
      writer.println("?>");
   }

   public void text(DocumentTableEntry entry) throws Exception {
      int valID = entry.getValID();
      writeEscapedText(UTF8.toCharArray(table.getValue(valID)));
   }

   public void cdata(DocumentTableEntry entry) throws Exception {
      int valID = entry.getValID();
      writer.print("<![CDATA[");
      UTF8.toWriter(table.getValue(valID), writer);
      writer.print("]]>");
   }

   public void comment(DocumentTableEntry entry) throws Exception {
      int valID = entry.getValID();
      writer.print("<!--");
      UTF8.toWriter(table.getValue(valID), writer);
      writer.print("-->");
   }

   public void docType(DocumentTableEntry entry) {
      processor.next(); // Eat the OBJ_END_DOCTYPE
   }

   public void entity(DocumentTableEntry entry) {
      int symID = entry.getValID();
      String name = symbols.getQName(symID).getName();
      entry = processor.next(); // Get Public ID
      int valID = entry.getValID();
      String publicID = table.getValue(valID).toString();
      entry = processor.next(); // Get System ID
      valID = entry.getValID();
      String systemID = table.getValue(valID).toString();
      entry = processor.next(); // Get Notation Name
      valID = entry.getValID();
      String notation = table.getValue(valID).toString();
      processor.next(); // Eat the OBJ_END_ENTITY
      /** @todo Figure out how to create an Entity node with Text */
   }

   public void entityRef(DocumentTableEntry entry) {
      /** @todo This */
   }

   public void notation(DocumentTableEntry entry) {
      int symID = entry.getValID();
      String name = symbols.getQName(symID).getName();
      entry = processor.next(); // Get Public ID
      int valID = entry.getValID();
      String publicID = table.getValue(valID).toString();
      entry = processor.next(); // Get System ID
      valID = entry.getValID();
      String systemID = table.getValue(valID).toString();
      processor.next(); // Eat the OBJ_END_NOTATION

      writer.print("<!NOTATION ");
      writer.print(name);
      if ( publicID != null && publicID.length() > 0 ) {
         writer.print(" PUBLIC ");
         writer.print(publicID);
         if ( systemID != null && systemID.length() > 0 ) {
            writer.print(' ');
            writer.print(systemID);
         }
      }
      else if ( systemID != null && systemID.length() > 0 ) {
         writer.print(" SYSTEM ");
         writer.print(systemID);
      }
      writer.print('>');
   }

   private void writeEscapedText(char[] value) throws Exception {
      String outval = null;
      int start = 0;
      int len = 0;
      for ( int i = 0; i < value.length; i++ ) {
         char c = value[i];
         switch ( c ) {
            case '&':
               outval = "&amp;";
               break;
            case '\'':
               outval = "&apos;";
               break;
            case '\"':
               outval = "&quot;";
               break;
            case '<':
               outval = "&lt;";
               break;
            case '>':
               outval = "&gt;";
               break;

            case ' ':
            case '\n':
            case '\r':
            case '\t':
               len++;
               continue;

            default:
               if ( c < ' ' || c == 127 ) {
                  outval = "&#x" + Integer.toHexString(c) + ";";
                  break;
               }
               else {
                  len++;
                  continue;
               }
         }

         if ( outval != null ) {
            if ( len > 0 )
               writer.write(value, start, len);
            writer.write(outval);
            start = i + 1;
            len = 0;
            outval = null;
         }
      }
      if ( len > 0 )
         writer.write(value, start, len);
   }
}
