/*
------------------------------------------------------------------------------
MetaCam - Extract EXIF information from digital camera files, with
support for Vendor specific blocks.
Copyright (C) 2000 Daniel Stephens (daniel@cheeseplant.org)

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.
------------------------------------------------------------------------------
*/

static const char *rcsid __attribute__((unused))="$Id: ocontext.cc,v 1.5 2004/08/21 17:23:24 daniel Exp $";

#include "odrivers.h"

#include <ctype.h>

OutputContext::~OutputContext()
{
}

OutputDriver::~OutputDriver()
{
}

DisplayOutputContext::~DisplayOutputContext()
{
}

ostream &
DisplayOutputContext::startBlock(const char *hdr) 
{
    static const char *static_pad = "                            ";
    static const int padl = strlen(static_pad);

    if (in_block) endBlock();

    int hlen = strlen(hdr);
    if (hlen < padl) {
	o_stream << &static_pad[hlen];
    }

    o_stream << hdr << ": ";
    in_block = true;
    return o_stream;
}

ostream &
DisplayOutputContext::startBlock(const string &hdr) 
{
    static const char *static_pad = "                            ";
    static const int padl = strlen(static_pad);

    if (in_block) endBlock();

    int hlen = hdr.length();
    if (hlen < padl) {
	o_stream << &static_pad[hlen];
    }

    o_stream << hdr << ": ";
    in_block = true;
    return o_stream;
}

ostream &
DisplayOutputContext::os() 
{ 
    if (!in_block) {
	cerr << "Attempted to access output stream outside of block!"
	     << endl;
	in_block = true;
	    
    }
    return o_stream;
}

void
DisplayOutputContext::endBlock() 
{
    if (!in_block) return;
    os() << endl;
    in_block = false;
}

DisplayOutputDriver::~DisplayOutputDriver()
{
    // Nothing
}

void
DisplayOutputDriver::startFile(const char *fname)
{
    os << "File: " << fname << endl;
}

OutputContext *
DisplayOutputDriver::startGroup(const char *header)
{
    static const char *DASH_LINE = "--------------------------------------------------";
    static const int DASH_LEN = strlen(DASH_LINE);

    if (current_context) endGroup();

    int hlen = strlen(header);
    os << "  " << header;
    if (hlen < DASH_LEN) {
	os << " " << &DASH_LINE[hlen];
    }
    os << endl;

    return new DisplayOutputContext(os);
}

OutputContext *
DisplayOutputDriver::startGroup(const string &header)
{
    static const char *DASH_LINE = "--------------------------------------------------";
    static const int DASH_LEN = strlen(DASH_LINE);

    if (current_context) endGroup();

    int hlen = header.length();
    os << "  " << header;
    if (hlen < DASH_LEN) {
	os << " " << &DASH_LINE[hlen];
    }
    os << endl;

    return new DisplayOutputContext(os);
}

void 
DisplayOutputDriver::endGroup()
{
    if (current_context) {
	current_context->endBlock();
	delete current_context;
	current_context = 0;
    }
}

void
DisplayOutputDriver::endFile()
{
    endGroup();
    os << endl;
}

void 
DisplayOutputDriver::close() 
{
    endFile();
}



XMLOutputContext::~XMLOutputContext()
{
    // Does nothing
}

ostream &
XMLOutputContext::startBlock(const char *hdr) 
{
    if (in_block) endBlock();

    driver.outputRaw("      <value");
    driver.outputAttribute("label", hdr);

    in_block = true;
    
    str = new ostringstream();
    return *str;
}

ostream &
XMLOutputContext::startBlock(const string &hdr) 
{
    if (in_block) endBlock();

    driver.outputRaw("      <value");
    driver.outputAttribute("label", hdr);

    in_block = true;
    str = new ostringstream();
    return *str;
}

ostream &
XMLOutputContext::os() 
{ 
    if (!in_block) {
	cerr << "Attempted to access output stream outside of block!"
	     << endl;
	abort();
    }
    return *str;
}

void
XMLOutputContext::endBlock() 
{
    if (!in_block) return;
    (*str) << ends;
    driver.outputAttribute("value", str->str());
    driver.outputLine("/>");

    delete str;
    str = 0;

    in_block = false;
}

XMLOutputDriver::XMLOutputDriver(ostream &o) : os(o), current_context(0),
					       is_open(true), in_file(false)
{
    outputLine("<?xml version='1.0' encoding='utf-8'?>");
    outputLine("<metacam>");

}

XMLOutputDriver::~XMLOutputDriver()
{
    if (is_open) close();
}

void
XMLOutputDriver::startFile(const char *fname)
{
    if (in_file) endFile();
    outputRaw("  <file");
    outputAttribute("filename", fname);
    outputLine(">");
    in_file = true;
}

OutputContext *
XMLOutputDriver::startGroup(const char *header)
{
    if (current_context) endGroup();
    
    outputRaw("    <group");
    outputAttribute("heading", header);
    outputLine(">");

    current_context = new XMLOutputContext(*this);
    return current_context;
}

OutputContext *
XMLOutputDriver::startGroup(const string &header)
{
    if (current_context) endGroup();
    
    outputRaw("    <group");
    outputAttribute("heading", header);
    outputLine(">");

    current_context = new XMLOutputContext(*this);
    return current_context;
}

void 
XMLOutputDriver::endGroup()
{
    if (current_context) {
	current_context->endBlock();
	delete current_context;
	current_context = 0;
	outputLine("    </group>");
    }
}

void
XMLOutputDriver::endFile()
{
    if (in_file) {
	endGroup();
	outputLine("  </file>");
	in_file = false;
    }
}

void 
XMLOutputDriver::close() 
{
    if (is_open) {
	endFile();
	outputLine("</metacam>");
	is_open =  false;
    }
}

static const char *HEXES = "0123456789ABCDEF";

void
XMLOutputDriver::outputCooked(const string &buf)
{
    outputCooked(buf.data());
}

void
XMLOutputDriver::outputCooked(const char *buf)
{
    char tmphex[8];
    int last_output = 0;
    int i;
    for (i=0; buf[i]; ++i) {
	char c= buf[i];
	if (isalnum(c)) continue;
	const char *encode =0;
	if ((c < ' ') || (c > 126)) {
	    tmphex[0] = '&';
	    tmphex[1] = '#';
	    tmphex[2] = HEXES[(c >> 4) & 15];
	    tmphex[3] = HEXES[c & 15];
	    tmphex[4] = 0;
	    encode = tmphex;
	} else if (c == '&') {
	    encode = "&amp;";
	} else if (c == '<') {
	    encode = "&lt;";
	} else if (c == '>') {
	    encode = "&gt;";
	} else if (c == '\"') {
	    encode = "&quot;";
	}

	if (!encode) continue;
	
	if (last_output < i) {
	    os.write(&buf[last_output], i);
	}
	os << encode;
	last_output = i+1;
    }
    if (last_output < i) {
	os.write(&buf[last_output], i);
    }
}
