// Copyright 1997-2000 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OHTableBuilder.h"

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>
#import <OWF/OWF.h>

#import "OHColorPalette.h"
#import "OWSGMLDTD-OHHTMLDTD.h"
#import "OHHTMLDisplayProcessor.h"
#import "OHHTMLDocument.h"
#import "OHHTMLOwner.h"
#import "OHTable.h"
#import "OHTableCellHTMLView.h"
#import "OHTextBuilder.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/Tables.subproj/OHTableBuilder.m,v 1.13 2000/01/20 00:28:02 kc Exp $")

#define DEFAULT_CELLPADDING 1.0
#define DEFAULT_CELLSPACING 2.0

@interface OHTableBuilder (Private)

+ (void)setupDTD;

- (void)cellBoundary:(OWSGMLTag *)cellTag;
- (void)newCell:(OWSGMLTag *)cellTag;
- (void)rowBoundary;
- (void)newRow;

- (void)setCellAttributesFromTDTag:(OWSGMLTag *)tag;
- (void)setCellAttributesFromTRTag:(OWSGMLTag *)tag;
- (void)setCellAttributesFromCaptionTag:(OWSGMLTag *)tag;
- (void)setVerticalAlignmentFromString:(NSString *)verticalAlignment;
- (void)setHorizontalAlignmentFromString:(NSString *)horizontalAlign;
- (void)setCellTypeFromTag:(OWSGMLTag *)tag;

@end

static NSNumber *bit;

@implementation OHTableBuilder

+ (void)didLoad;
{
    [self setupDTD];
}

+ (void)initialize;
{
    static BOOL initialized = NO;

    [super initialize];
    if (initialized)
	return;
    initialized = YES;

    bit = [[NSNumber numberWithInt:1] retain];
}

static OWSGMLTagType *tableTagType;
static OWSGMLTagType *tdTagType;
static OWSGMLTagType *thTagType;
static OWSGMLTagType *trTagType;
static OWSGMLTagType *captionTagType;
#ifdef SPEC_SUPPORT
static OWSGMLTagType *hspecTagType;
static OWSGMLTagType *vspecTagType;
#endif
#if 0
static OWSGMLTagType *tbodyTagType;
static OWSGMLTagType *theadTagType;
static OWSGMLTagType *tfootTagType;
#endif

unsigned int tableAlignAttributeIndex;
unsigned int tableHSpaceAttributeIndex;
unsigned int tableVSpaceAttributeIndex;
static unsigned int tableWidthAttributeIndex;
static unsigned int tableHeightAttributeIndex;
static unsigned int tableBorderAttributeIndex;
static unsigned int tableCellSpacingAttributeIndex;
static unsigned int tableCellPaddingAttributeIndex;
static unsigned int tableBorderStyleAttributeIndex;
static unsigned int tableBGColorAttributeIndex;
static unsigned int tableTextAttributeIndex;
static unsigned int tableLinkAttributeIndex;
static unsigned int tableVLinkAttributeIndex;
static unsigned int tableALinkAttributeIndex;
static unsigned int trAlignAttributeIndex;
static unsigned int trVAlignAttributeIndex;
static unsigned int trBGColorAttributeIndex;
static unsigned int trTextAttributeIndex;
static unsigned int trLinkAttributeIndex;
static unsigned int trVLinkAttributeIndex;
static unsigned int trALinkAttributeIndex;
static unsigned int tdNoWrapAttributeIndex;
static unsigned int tdRowSpanAttributeIndex;
static unsigned int tdColSpanAttributeIndex;
static unsigned int tdAlignAttributeIndex;
static unsigned int tdVAlignAttributeIndex;
static unsigned int tdWidthAttributeIndex;
static unsigned int tdHeightAttributeIndex;
static unsigned int tdBGColorAttributeIndex;
static unsigned int tdBackgroundAttributeIndex;
static unsigned int tdTextAttributeIndex;
static unsigned int tdLinkAttributeIndex;
static unsigned int tdVLinkAttributeIndex;
static unsigned int tdALinkAttributeIndex;
static unsigned int captionAlignAttributeIndex;
static unsigned int captionBGColorAttributeIndex;
static unsigned int captionTextAttributeIndex;
static unsigned int captionLinkAttributeIndex;
static unsigned int captionVLinkAttributeIndex;
static unsigned int captionALinkAttributeIndex;

- initWithProcessor:(OHHTMLDisplayProcessor *)aProcessor tag:(OWSGMLTag *)aTableTag;
{
    NSString *attributeValue;

    if (![super init])
	return nil;

    htmlProcessor = [aProcessor retain];
    table = [[OHTable allocWithZone:[[htmlProcessor htmlDocument] zone]] init];

    oldBuilder = [[htmlProcessor textBuilder] retain];

    currentCellOwner = nil;
    currentCellHTMLView = nil;
    cellBuilder = nil;
#ifdef SPEC_SUPPORT
    specs = [[NSMutableArray alloc] init];
#endif

    cellsThisRow = 0;
    currentRow = 0;
    flags.cellIsReal = NO;
    flags.rowIsReal = NO;

    // Table-related preference
    flags.implicitCellsOK = ![[OFUserDefaults sharedUserDefaults] boolForKey:@"OHShuffleImplicitTableCells"];

    cellPadding = DEFAULT_CELLPADDING;

    // Some things get applied to the cells
    tableTag = [aTableTag retain];

    // Process the TABLE tag attributes
    flags.tableHasBackgroundColor = sgmlTagAttributePresentAtIndex(tableTag, tableBGColorAttributeIndex);
    tableColorPalette = [[oldBuilder colorPalette] copyWithChangesFromTag:tableTag bgColorAttributeIndex:tableBGColorAttributeIndex textAttributeIndex:tableTextAttributeIndex linkAttributeIndex:tableLinkAttributeIndex vlinkAttributeIndex:tableVLinkAttributeIndex alinkAttributeIndex:tableALinkAttributeIndex];

    if (flags.tableHasBackgroundColor)
	[table setBackgroundColor:[tableColorPalette backgroundColor]];

    if (sgmlTagAttributePresentAtIndex(tableTag, tableBorderAttributeIndex)) {
	NSString *tableBorderValue;

	tableBorderValue = sgmlTagValueForAttributeAtIndex(tableTag, 
			    tableBorderAttributeIndex);
	if (tableBorderValue && [tableBorderValue length] > 0) {
	    float borderWidth;
		
            switch ([tableBorderValue characterAtIndex:0]) {
                default:
                case 'a': // all
                case 'A':
                    [table setBorderType:OWTBorderAll];
                    break;
                case 'n': // none
                case 'N':
                    [table setBorderType:OWTBorderNone];
                    break;
                case 'f': // frame
                case 'F':
                    [table setBorderType:OWTBorderFrame];
                    break;
                case 'b': // basic
                case 'B':
                    [table setBorderType:OWTBorderSections];
                    break;
                case 'r': // rows
                case 'R':
                    [table setBorderType:OWTBorderFrame | OWTBorderSections | OWTBorderRows];
                    break;
                case 'c': // cols
                case 'C':
                    [table setBorderType:OWTBorderFrame | OWTBorderSections | OWTBorderColumns];
                    break;
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                case '.':
                case ' ':
                    borderWidth = [tableBorderValue floatValue];
                    [table setBorderWidth:borderWidth];
                    if (borderWidth > 0.1)
                        [table setBorderType:OWTBorderAll];
                        break;
            }
	} else {
	    // border attribute present, but no value specified
	    [table setBorderType:OWTBorderAll];
	}
    }

    attributeValue = sgmlTagValueForAttributeAtIndex(tableTag, tableWidthAttributeIndex);
    if (attributeValue) {
        float tableWidth;

        tableWidth = [attributeValue floatValue];
        if (tableWidth) {
            if ([attributeValue hasSuffix:@"%"])
                [table setTableWidth:tableWidth / 100.0 isFraction:YES];
            else
                [table setTableWidth:tableWidth isFraction:NO];
        }
    }
    attributeValue = sgmlTagValueForAttributeAtIndex(tableTag, tableHeightAttributeIndex);
    if (attributeValue) {
        float tableHeight;

        tableHeight = [attributeValue floatValue];
        if (tableHeight) {
            if ([attributeValue hasSuffix:@"%"])
                [table setTableHeight:tableHeight / 100.0 isFraction:YES];
            else
                [table setTableHeight:tableHeight isFraction:NO];
        }
    }
    attributeValue = sgmlTagValueForAttributeAtIndex(tableTag, tableCellSpacingAttributeIndex);
    if (attributeValue) {
        [table setCellSpacing:[attributeValue floatValue]];
    } else {
        [table setCellSpacing:DEFAULT_CELLSPACING];
    }

    attributeValue = sgmlTagValueForAttributeAtIndex(tableTag, tableCellPaddingAttributeIndex);
    if (attributeValue)
        cellPadding = [attributeValue floatValue];

    attributeValue = sgmlTagValueForAttributeAtIndex(tableTag, tableBorderStyleAttributeIndex);
    if (attributeValue && [attributeValue length] > 0) {
        switch ([attributeValue characterAtIndex:0]) {
	case 'f': // flat
	case 'F':
	    [table setDisplayStyle:OWTD_Flat];
	    break;
	case 'g': // gray
	case 'G':
	    [table setDisplayStyle:OWTD_Gray];
	    break;
	case 'c': // colored
	case 'C':
	    [table setDisplayStyle:OWTD_Colored];
	    break;
	case 't': // textured
	case 'T':
	    [table setDisplayStyle:OWTD_Textured];
	    break;
	default:
	    break;
	}
    }


    [self newCell:nil];

    // TODO: ID, NOWRAP, ALIGN

    // This has an object in every slot that the table has a cell, so that we can position new cells without having any un-threadsafe access to the table (in fact, we don't talk to the table directly at all after we finish init)
    cellBitmap = [[OFMatrix alloc] init];

    return self;
}

- (void)dealloc
{
    [table release];
    [htmlProcessor release];
    [oldBuilder release];
    [cellBuilder release];
    [currentCellOwner release];
#ifdef SPEC_SUPPORT
    [specs release];
#endif
    [rowTag release];
    [tableTag release];
    [cellBitmap release];
    [tableColorPalette release];
    [super dealloc];
}

- (OHTable *)table
{
    return table;
}

- (void)processTag:(OWSGMLTag *)tag;
{
    OWSGMLTagType *tagType;
    
    tagType = [tag tagType];
    if (tagType == tdTagType || tagType == thTagType) {
	[self cellBoundary:tag];
	if ([tag tokenType] == OWSGMLTokenTypeStartTag) {
	    [self setCellAttributesFromTDTag:tag];
	}
    } else if (tagType == captionTagType) {
	[self cellBoundary:tag];
	if ([tag tokenType] == OWSGMLTokenTypeStartTag) {
	    [self setCellAttributesFromCaptionTag:tag];
	}
    } else if (tagType == trTagType) {
	[self rowBoundary];
	if ([tag tokenType] == OWSGMLTokenTypeStartTag) {
            flags.rowIsReal = YES;
	    [rowTag release];
	    rowTag = [tag retain];
	    [self setCellAttributesFromTRTag:rowTag];
	}
#ifdef SPEC_SUPPORT
    } else if (tagType == hspecTagType || tagType == vspecTagType) {
	if ([tag tokenType] == OWSGMLTokenTypeStartTag) {
	    [specs addObject:tag];
	    [self setCellAttributesFromSpecTag:tag];
	}
#endif
    } else {
#ifdef DEBUG
	NSLog(@"Table: unimplemented tag %@", [tag name]);
#endif
    }
}

- (void)endBuilding;
{
    [self cellBoundary:nil];

    [htmlProcessor setTextBuilder:oldBuilder];
    [oldBuilder release];
    oldBuilder = nil;

    [htmlProcessor release];
    htmlProcessor = nil;
}

@end

@implementation OHTableBuilder (Private)

+ (void)setupDTD;
{
    OWSGMLDTD *dtd;

    dtd = [OWSGMLDTD OHHTMLDTD];

    tableTagType = [dtd tagTypeNamed:@"table"];
    tdTagType = [dtd tagTypeNamed:@"td"];
    thTagType = [dtd tagTypeNamed:@"th"];
    trTagType = [dtd tagTypeNamed:@"tr"];
    captionTagType = [dtd tagTypeNamed:@"caption"];
#ifdef SPEC_SUPPORT
    hspecTagType = [dtd tagTypeNamed:@"hspec"];
    vspecTagType = [dtd tagTypeNamed:@"vspec"];
#endif
#if 0
    tbodyTagType = [dtd tagTypeNamed:@"tbody"];
    theadTagType = [dtd tagTypeNamed:@"thead"];
    tfootTagType = [dtd tagTypeNamed:@"tfoot"];
#endif

    tableAlignAttributeIndex = [tableTagType addAttributeNamed:@"align"];
    tableHSpaceAttributeIndex = [tableTagType addAttributeNamed:@"hspace"];
    tableVSpaceAttributeIndex = [tableTagType addAttributeNamed:@"vspace"];
    tableWidthAttributeIndex = [tableTagType addAttributeNamed:@"width"];
    tableHeightAttributeIndex = [tableTagType addAttributeNamed:@"height"];
    tableBorderAttributeIndex = [tableTagType addAttributeNamed:@"border"];
    tableCellSpacingAttributeIndex = [tableTagType addAttributeNamed:@"cellspacing"];
    tableCellPaddingAttributeIndex = [tableTagType addAttributeNamed:@"cellpadding"];
    tableBorderStyleAttributeIndex = [tableTagType addAttributeNamed:@"borderstyle"];
    tableBGColorAttributeIndex = [tableTagType addAttributeNamed:@"bgcolor"];
    tableTextAttributeIndex = [tableTagType addAttributeNamed:@"text"];
    tableLinkAttributeIndex = [tableTagType addAttributeNamed:@"link"];
    tableVLinkAttributeIndex = [tableTagType addAttributeNamed:@"vlink"];
    tableALinkAttributeIndex = [tableTagType addAttributeNamed:@"alink"];

    trAlignAttributeIndex = [trTagType addAttributeNamed:@"align"];
    trVAlignAttributeIndex = [trTagType addAttributeNamed:@"valign"];
    trBGColorAttributeIndex = [trTagType addAttributeNamed:@"bgcolor"];
    trTextAttributeIndex = [trTagType addAttributeNamed:@"text"];
    trLinkAttributeIndex = [trTagType addAttributeNamed:@"link"];
    trVLinkAttributeIndex = [trTagType addAttributeNamed:@"vlink"];
    trALinkAttributeIndex = [trTagType addAttributeNamed:@"alink"];

    tdNoWrapAttributeIndex = [tdTagType addAttributeNamed:@"nowrap"];
    tdRowSpanAttributeIndex = [tdTagType addAttributeNamed:@"rowspan"];
    tdColSpanAttributeIndex = [tdTagType addAttributeNamed:@"colspan"];
    tdAlignAttributeIndex = [tdTagType addAttributeNamed:@"align"];
    tdVAlignAttributeIndex = [tdTagType addAttributeNamed:@"valign"];
    tdWidthAttributeIndex = [tdTagType addAttributeNamed:@"width"];
    tdHeightAttributeIndex = [tdTagType addAttributeNamed:@"height"];
    tdBGColorAttributeIndex = [tdTagType addAttributeNamed:@"bgcolor"];
    tdBackgroundAttributeIndex = [tdTagType addAttributeNamed:@"background"];
    tdTextAttributeIndex = [tdTagType addAttributeNamed:@"text"];
    tdLinkAttributeIndex = [tdTagType addAttributeNamed:@"link"];
    tdVLinkAttributeIndex = [tdTagType addAttributeNamed:@"vlink"];
    tdALinkAttributeIndex = [tdTagType addAttributeNamed:@"alink"];

    [thTagType shareAttributesWithTagType:tdTagType];

    captionAlignAttributeIndex = [captionTagType addAttributeNamed:@"align"];
    captionBGColorAttributeIndex = [captionTagType addAttributeNamed:@"bgcolor"];
    captionTextAttributeIndex = [captionTagType addAttributeNamed:@"text"];
    captionLinkAttributeIndex = [captionTagType addAttributeNamed:@"link"];
    captionVLinkAttributeIndex = [captionTagType addAttributeNamed:@"vlink"];
    captionALinkAttributeIndex = [captionTagType addAttributeNamed:@"alink"];
}

//

- (void)cellBoundary:(OWSGMLTag *)cellTag;
{
    if (flags.cellIsReal || (cellBuilder && ![cellBuilder isPristine]))
        [self newCell:cellTag];
    else {
        if (cellBuilder && [cellBuilder isPristine]) {
            // XXX TODO: The -isPristine method is now a misnomer; it should really be called something like -hasContent (but it should have the same semantics; in particular, a <BR> doesn't count as content.) There also needs to be a method to make the builder _actually_ pristine (-unsully ?) which would get rid of any <BR>s and the like, setting the text builder back to its initial state, including character styles and such.
            [htmlProcessor setTextBuilder:oldBuilder];
            [cellBuilder endTextBuild];
            [cellBuilder release];
            [currentCellOwner release];
            cellBuilder = nil;
            currentCellOwner = nil;
            currentCellHTMLView = nil;
#ifdef DEBUG
            NSLog(@"Bein' slow in tables.");
#endif
            [self newCell:cellTag];
        } else if (!cellBuilder) {
            [self newCell:cellTag];
        } else {
            [self setCellTypeFromTag:cellTag];
        }
    }
}

- (void)newCell:(OWSGMLTag *)cellTag;
{
    OWSGMLTagType *cellTagType;

#if 0
    NSLog(@"Table 0x%x newCell, tag=%@ %d", self, [cellTag name], [cellTag tokenType]);
#endif

    if (currentCellHTMLView) {
        unsigned int cellColumn;

        // Finish off the cell
        [htmlProcessor endAnchor];
        [htmlProcessor setTextBuilder:oldBuilder];
        [cellBuilder endTextBuild];
        [cellBuilder release];
        cellBuilder = nil;

        if ([currentCellHTMLView cellType] != CellType_Caption) {
             // Fix up the cell & insert it into the table (find out where it is!)
            cellColumn = 0;
            while ([cellBitmap objectAtRowIndex:currentRow columnIndex:cellColumn])
                cellColumn++;
            [currentCellHTMLView setColumnIndex:cellColumn];
            [currentCellHTMLView setRowIndex:currentRow];
            [cellBitmap setObject:bit atRowIndex:currentRow span:[currentCellHTMLView rowSpan] columnIndex:cellColumn span:[currentCellHTMLView columnSpan]];

            cellsThisRow++;
        }

        [table mainThreadPerformSelector:@selector(acceptTableCellOwner:) withObject:currentCellOwner];
        [currentCellOwner release];
        currentCellOwner = nil;
        currentCellHTMLView = nil;
    }

    if (flags.implicitCellsOK ||
        ([cellTag tokenType] == OWSGMLTokenTypeStartTag && ((cellTagType = [cellTag tagType]) != nil) && (cellTagType == tdTagType || cellTagType == thTagType || cellTagType == captionTagType))) {
#ifdef SPEC_SUPPORT
        unsigned int specIndex, specCount;
#endif

        // Create a new cell, etc.
        currentCellOwner = [[OHHTMLOwner allocWithZone:[table zone]] initWithHTMLViewClass:[OHTableCellHTMLView class] htmlDocument:[htmlProcessor htmlDocument]];
        currentCellHTMLView = (id)[currentCellOwner htmlView];
        cellBuilder = [[OHTextBuilder allocWithZone:[self zone]] initWithHTMLOwner:currentCellOwner colorPalette:tableColorPalette];
        [htmlProcessor setTextBuilder:cellBuilder];

        flags.cellIsReal = NO; // unless explicitly stated...

        // Set defaults
        [currentCellHTMLView setTextContainerInset:NSMakeSize(cellPadding, cellPadding)];
        if (flags.tableHasBackgroundColor)
            [currentCellHTMLView setUsesOwnBackgroundColor:YES];

        // Tell the cell where it is
        [self setCellTypeFromTag:cellTag];
        if (rowTag)
            [self setCellAttributesFromTRTag:rowTag];
#ifdef SPEC_SUPPORT
        specCount = [specs count];
        for (specIndex = 0; specIndex < specCount; specIndex++) {
            [self setCellAttributesFromSpecTag:
             [specs objectAtIndex:specIndex]];
        }
#endif
    }
}

- (void)rowBoundary;
{
    [self cellBoundary:nil];
    if (flags.rowIsReal || cellsThisRow > 0)
        [self newRow];
}

- (void)newRow;
{
    [self cellBoundary:nil];
    currentRow++;
    cellsThisRow = 0;
    flags.rowIsReal = NO;
    if (rowTag) {
        [rowTag release];
        rowTag = nil;
    }
}

//

- (void)setCellAttributesFromTDTag:(OWSGMLTag *)tag;
{
    NSString *attributeValue;
    OHColorPalette *cellPalette;
    NSString *background;

    if ((attributeValue = sgmlTagValueForAttributeAtIndex(tag, tdColSpanAttributeIndex)))
        [currentCellHTMLView setColumnSpan:[attributeValue intValue]];
    if ((attributeValue = sgmlTagValueForAttributeAtIndex(tag, tdRowSpanAttributeIndex)))
        [currentCellHTMLView setRowSpan:[attributeValue intValue]];
    if (sgmlTagAttributePresentAtIndex(tag, tdNoWrapAttributeIndex))
        [cellBuilder setLineBreakMode:NSLineBreakByClipping];

    if ((attributeValue = sgmlTagValueForAttributeAtIndex(tag, tdWidthAttributeIndex))) {
        int cellWidth;

        cellWidth = [attributeValue intValue];
        if (cellWidth > 0)
            [currentCellHTMLView requestWidth:cellWidth isFraction:[attributeValue hasSuffix:@"%"]];
    }
    [self setVerticalAlignmentFromString:sgmlTagValueForAttributeAtIndex(tag, tdVAlignAttributeIndex)];
    [self setHorizontalAlignmentFromString:sgmlTagValueForAttributeAtIndex(tag, tdAlignAttributeIndex)];
    cellPalette = [[cellBuilder colorPalette] copyWithChangesFromTag:tag bgColorAttributeIndex:tdBGColorAttributeIndex textAttributeIndex:tdTextAttributeIndex linkAttributeIndex:tdLinkAttributeIndex vlinkAttributeIndex:tdVLinkAttributeIndex alinkAttributeIndex:tdALinkAttributeIndex];
    if (sgmlTagAttributePresentAtIndex(tag, tdBGColorAttributeIndex))
        [currentCellHTMLView setUsesOwnBackgroundColor:YES];
    [cellBuilder setColorPalette:cellPalette];

    background = sgmlTagValueForAttributeAtIndex(tag, tdBackgroundAttributeIndex);
    if (background) {
        [currentCellHTMLView setBackgroundImageAddress:[[htmlProcessor baseAddress] addressForRelativeString:background]];
        [currentCellHTMLView setUsesOwnBackgroundColor:YES];
    }


    [cellPalette release];
}

- (void)setCellAttributesFromTRTag:(OWSGMLTag *)tag;
{
    OHColorPalette *cellPalette;

    [self setVerticalAlignmentFromString:sgmlTagValueForAttributeAtIndex(tag, trVAlignAttributeIndex)];
    [self setHorizontalAlignmentFromString:sgmlTagValueForAttributeAtIndex(tag, trAlignAttributeIndex)];
    cellPalette = [[cellBuilder colorPalette] copyWithChangesFromTag:tag bgColorAttributeIndex:trBGColorAttributeIndex textAttributeIndex:trTextAttributeIndex linkAttributeIndex:trLinkAttributeIndex vlinkAttributeIndex:trVLinkAttributeIndex alinkAttributeIndex:trALinkAttributeIndex];
    if (sgmlTagAttributePresentAtIndex(tag, trBGColorAttributeIndex))
        [currentCellHTMLView setUsesOwnBackgroundColor:YES];
    [cellBuilder setColorPalette:cellPalette];
    [cellPalette release];
}

- (void)setCellAttributesFromCaptionTag:(OWSGMLTag *)tag;
{
    OHColorPalette *cellPalette;

    [self setVerticalAlignmentFromString:sgmlTagValueForAttributeAtIndex(tag, captionAlignAttributeIndex)];
#warning Can captions actually have text color attributes?
    cellPalette = [[cellBuilder colorPalette] copyWithChangesFromTag:tag bgColorAttributeIndex:captionBGColorAttributeIndex textAttributeIndex:captionTextAttributeIndex linkAttributeIndex:captionLinkAttributeIndex vlinkAttributeIndex:captionVLinkAttributeIndex alinkAttributeIndex:captionALinkAttributeIndex];
    if (sgmlTagAttributePresentAtIndex(tag, captionBGColorAttributeIndex))
        [currentCellHTMLView setUsesOwnBackgroundColor:YES];
    [cellBuilder setColorPalette:cellPalette];
    [cellPalette release];
}

- (void)setVerticalAlignmentFromString:(NSString *)verticalAlignment;
{
    /* From http://www.w3.org/TR/REC-html40/struct/tables.html:

    This attribute specifies the vertical position of data within a cell. Possible values:

    * top: Cell data is flush with the top of the cell.
    * middle: Cell data is centered vertically within the cell. This is the default value.
    * bottom: Cell data is flush with the bottom of the cell.
    * baseline: All cells in the same row as a cell whose valign attribute has this value should have their textual data positioned so that the first text line occurs on a baseline common to all cells in the row. This constraint does not apply to subsequent text lines in these cells.

    */


    switch ([verticalAlignment firstCharacter]) {
        case 't': // top
        case 'T':
            [currentCellHTMLView setVerticalAlignment:OHTableCellVerticalAlignTop];
            break;
        case 'b': // "bottom" or "baseline"
        case 'B':
            if ([verticalAlignment length] >= 2) {
                switch ([verticalAlignment characterAtIndex:1]) {
                    case 'a': // "baseline"
                    case 'A':
                        [currentCellHTMLView setVerticalAlignment:OHTableCellVerticalAlignBaseline];
                        break;
                    case 'o': // "bottom"
                    case 'O':
                        [currentCellHTMLView setVerticalAlignment:OHTableCellVerticalAlignBottom];
                        break;
                }
            } else {
                // "b" is an invalid alignment
            }
            break;
        case 'm': // "middle"
        case 'M':
        case 'c': // "center"
        case 'C':
            [currentCellHTMLView setVerticalAlignment:OHTableCellVerticalAlignMiddle];
            break;
        default:
            break;

    }
}

- (void)setHorizontalAlignmentFromString:(NSString *)horizontalAlign;
{
    switch ([horizontalAlign firstCharacter]) {
        case 'l': // left
        case 'L':
            [cellBuilder setTextAlignment:OW_LEFTALIGNED];
            break;
        case 'c': // center
        case 'C':
        case 'm': // middle
        case 'M':
            [cellBuilder setTextAlignment:OW_CENTERED];
            break;
        case 'r': // right
        case 'R':
            [cellBuilder setTextAlignment:OW_RIGHTALIGNED];
            break;
    }
}

- (void)setCellTypeFromTag:(OWSGMLTag *)tag;
{
    OWSGMLTagType *tagType;

    if (!tag || [tag tokenType] != OWSGMLTokenTypeStartTag)
        return;

    tagType = [tag tagType];

    // Handle cell tags and their defaults
    if (tagType == tdTagType) {
        flags.cellIsReal = YES;
    } else if (tagType == thTagType) {
        [currentCellHTMLView setCellType:CellType_Header];
        [cellBuilder setFontBold:YES];
        [cellBuilder setTextAlignment:OW_CENTERED];
        flags.cellIsReal = YES;
    } else if (tagType == captionTagType) {
        [currentCellHTMLView setCellType:CellType_Caption];
        [cellBuilder setTextAlignment:OW_CENTERED];
        flags.cellIsReal = YES;
    }
}

@end
