// 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 "OHHTMLDisplayProcessor.h"

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

#import "OHBasicCell.h"
#import "OHBreak.h"
#import "OHColorPalette.h"
#import "OHHorizontalRule.h"
#import "OHHTMLDocument.h"
#import "OHHTMLPageView.h"
#import "OHInlineImageCell.h"
#import "OHSpacerCell.h"
#import "OWSGMLDTD-OHHTMLDTD.h"
#import "OHTextBuilder.h"
#import "OWScriptContext.h"
#import "OWScriptEvent.h"
#import "OWPipeline-ScriptExtensions.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/Building.subproj/OHHTMLDisplayProcessor-General.m,v 1.23 2000/01/20 00:27:45 kc Exp $")

@interface OHHTMLDisplayProcessor (Private)
- (void)processEndAnchorTag:(OWSGMLTag *)tag;
@end

@implementation OHHTMLDisplayProcessor (General)

static OWSGMLTagType *anchorTagType;
static OWSGMLTagType *bodyTagType;
static OWSGMLTagType *brTagType;
static OWSGMLTagType *hrTagType;
static OWSGMLTagType *htmlTagType;
static OWSGMLTagType *imgTagType;
static OWSGMLTagType *linkTagType;
static OWSGMLTagType *spacerTagType;

static unsigned int anchorIdAttributeIndex;
static unsigned int anchorNameAttributeIndex;
static unsigned int anchorHrefAttributeIndex;
static unsigned int anchorTargetAttributeIndex;
static unsigned int anchorEffectAttributeIndex;
static unsigned int anchorTitleAttributeIndex;
static unsigned int anchorOnClickAttributeIndex;
static unsigned int anchorOnMouseOutAttributeIndex;
static unsigned int anchorOnMouseOverAttributeIndex;
static unsigned int bodyBGColorAttributeIndex;
static unsigned int bodyTextAttributeIndex;
static unsigned int bodyLinkAttributeIndex;
static unsigned int bodyVLinkAttributeIndex;
static unsigned int bodyALinkAttributeIndex;
static unsigned int bodyBackgroundAttributeIndex;
static unsigned int bodyOnLoadAttributeIndex;
static unsigned int bodyOnUnloadAttributeIndex;
static unsigned int bodyTopMarginAttributeIndex;
static unsigned int bodyLeftMarginAttributeIndex;
static unsigned int bodyMarginWidthAttributeIndex;
static unsigned int bodyMarginHeightAttributeIndex;
static unsigned int brClearAttributeIndex;
static unsigned int hrAlignAttributeIndex;
static unsigned int hrSizeAttributeIndex;
static unsigned int hrWidthAttributeIndex;
static unsigned int hrSrcAttributeIndex;
static unsigned int hrNoshadeAttributeIndex;
static unsigned int imgAlignAttributeIndex;
static unsigned int imgIsMapAttributeIndex;
static unsigned int imgUseMapAttributeIndex;
static unsigned int imgHSpaceAttributeIndex;
static unsigned int imgVSpaceAttributeIndex;
static unsigned int imgWidthAttributeIndex;
static unsigned int imgHeightAttributeIndex;
static unsigned int imgAltAttributeIndex;
static unsigned int imgBorderAttributeIndex;
static unsigned int imgSrcAttributeIndex;
static unsigned int imgNameAttributeIndex;
static unsigned int imgOnLoadAttributeIndex;
static unsigned int imgOnErrorAttributeIndex;
static unsigned int imgOnAbortAttributeIndex;
static unsigned int spacerAlignAttributeIndex;
static unsigned int spacerTypeAttributeIndex;
static unsigned int spacerSizeAttributeIndex;
static unsigned int spacerHeightAttributeIndex;
static unsigned int spacerWidthAttributeIndex;

+ (void)didLoad;
{
    OWSGMLMethods *methods;
    OWSGMLDTD *dtd;

    dtd = [self dtd];

    anchorTagType = [dtd tagTypeNamed:@"a"];
    bodyTagType = [dtd tagTypeNamed:@"body"];
    brTagType = [dtd tagTypeNamed:@"br"];
    hrTagType = [dtd tagTypeNamed:@"hr"];
    htmlTagType = [dtd tagTypeNamed:@"html"];
    imgTagType = [dtd tagTypeNamed:@"img"];
    linkTagType = [dtd tagTypeNamed:@"link"];
    spacerTagType = [dtd tagTypeNamed:@"spacer"];

    [[dtd tagTypeNamed:@"image"] shareAttributesWithTagType:imgTagType];

    anchorIdAttributeIndex = [anchorTagType addAttributeNamed:@"id"];
    anchorNameAttributeIndex = [anchorTagType addAttributeNamed:@"name"];
    anchorHrefAttributeIndex = [anchorTagType addAttributeNamed:@"href"];
    anchorTargetAttributeIndex = [anchorTagType addAttributeNamed:@"target"];
    anchorEffectAttributeIndex = [anchorTagType addAttributeNamed:@"effect"];
    anchorTitleAttributeIndex = [anchorTagType addAttributeNamed:@"title"];
    anchorOnClickAttributeIndex = [anchorTagType addAttributeNamed:@"onclick"];
    anchorOnMouseOutAttributeIndex = [anchorTagType addAttributeNamed:@"onmouseout"];
    anchorOnMouseOverAttributeIndex = [anchorTagType addAttributeNamed:@"onmouseover"];

    bodyBGColorAttributeIndex = [bodyTagType addAttributeNamed:@"bgcolor"];
    bodyTextAttributeIndex = [bodyTagType addAttributeNamed:@"text"];
    bodyLinkAttributeIndex = [bodyTagType addAttributeNamed:@"link"];
    bodyVLinkAttributeIndex = [bodyTagType addAttributeNamed:@"vlink"];
    bodyALinkAttributeIndex = [bodyTagType addAttributeNamed:@"alink"];
    bodyBackgroundAttributeIndex = [bodyTagType addAttributeNamed:@"background"];
    bodyOnLoadAttributeIndex = [bodyTagType addAttributeNamed:@"onload"];
    bodyOnUnloadAttributeIndex = [bodyTagType addAttributeNamed:@"onunload"];
    bodyTopMarginAttributeIndex = [bodyTagType addAttributeNamed:@"topmargin"];
    bodyLeftMarginAttributeIndex = [bodyTagType addAttributeNamed:@"leftmargin"];
    bodyMarginWidthAttributeIndex = [bodyTagType addAttributeNamed:@"marginwidth"];
    bodyMarginHeightAttributeIndex = [bodyTagType addAttributeNamed:@"marginheight"];

    brClearAttributeIndex = [brTagType addAttributeNamed:@"clear"];

    hrAlignAttributeIndex = [hrTagType addAttributeNamed:@"align"];
    hrSizeAttributeIndex = [hrTagType addAttributeNamed:@"size"];
    hrWidthAttributeIndex = [hrTagType addAttributeNamed:@"width"];
    hrNoshadeAttributeIndex = [hrTagType addAttributeNamed:@"noshade"];
    hrSrcAttributeIndex = [hrTagType addAttributeNamed:@"src"];

    imgAlignAttributeIndex = [imgTagType addAttributeNamed:@"align"];
    imgIsMapAttributeIndex = [imgTagType addAttributeNamed:@"ismap"];
    imgUseMapAttributeIndex = [imgTagType addAttributeNamed:@"usemap"];
    imgHSpaceAttributeIndex = [imgTagType addAttributeNamed:@"hspace"];
    imgVSpaceAttributeIndex = [imgTagType addAttributeNamed:@"vspace"];
    imgWidthAttributeIndex = [imgTagType addAttributeNamed:@"width"];
    imgHeightAttributeIndex = [imgTagType addAttributeNamed:@"height"];
    imgAltAttributeIndex = [imgTagType addAttributeNamed:@"alt"];
    imgBorderAttributeIndex = [imgTagType addAttributeNamed:@"border"];
    imgSrcAttributeIndex = [imgTagType addAttributeNamed:@"src"];
    imgNameAttributeIndex = [imgTagType addAttributeNamed:@"name"];
    imgOnLoadAttributeIndex = [imgTagType addAttributeNamed:@"onload"];
    imgOnErrorAttributeIndex = [imgTagType addAttributeNamed:@"onerror"];
    imgOnAbortAttributeIndex = [imgTagType addAttributeNamed:@"onabort"];

    spacerAlignAttributeIndex = [spacerTagType addAttributeNamed:@"align"];
    spacerTypeAttributeIndex = [spacerTagType addAttributeNamed:@"type"];
    spacerSizeAttributeIndex = [spacerTagType addAttributeNamed:@"size"];
    spacerHeightAttributeIndex = [spacerTagType addAttributeNamed:@"height"];
    spacerWidthAttributeIndex = [spacerTagType addAttributeNamed:@"width"];

    // Registering link attributes even though they're not used by us (yet), so that the HTML source colorizer will know they're fine.
    [linkTagType addAttributeNamed:@"href"];
    [linkTagType addAttributeNamed:@"rel"];
    [linkTagType addAttributeNamed:@"rev"];

    methods = [self sgmlMethods];
    
    [methods registerMethod:@"Anchor" forTagName:@"a"];
    [methods registerMethod:@"EndAnchor" forEndTagName:@"a"];
    [methods registerMethod:@"Body" forTagName:@"body"];
    [methods registerMethod:@"Break" forTagName:@"br"];
    [methods registerMethod:@"Break" forEndTagName:@"br"];
    [methods registerMethod:@"HorizontalRule" forTagName:@"hr"];
    [methods registerMethod:@"HTML" forTagName:@"html"];
    [methods registerMethod:@"Image" forTagName:@"image"]; // Netscape
    [methods registerMethod:@"Image" forTagName:@"img"];
    [methods registerMethod:@"Link" forTagName:@"link"];
    [methods registerMethod:@"Spacer" forTagName:@"spacer"];
}

- (void)processAnchorTag:(OWSGMLTag *)tag;
{
    NSString *name, *handler;

    if ([textBuilder anchor])
	[self processEndAnchorTag:nil];

    name = sgmlTagValueForAttributeAtIndex(tag, anchorIdAttributeIndex);
    if (!name)
        name = sgmlTagValueForAttributeAtIndex(tag, anchorNameAttributeIndex);

    // Start the anchor
    [textBuilder startAnchorNamed:name address:[self addressForAnchorTag:tag]];

    // Set any event handlers
#define CHECK_ANCHOR_HANDLER(tagindex, eventname) \
    if ((handler = sgmlTagValueForAttributeAtIndex(tag, tagindex))) \
        [[self hintedScriptContext] setHandler:eventname onObject:[textBuilder anchor] parented:[pipeline scriptProxy] toString:handler];
    CHECK_ANCHOR_HANDLER(anchorOnClickAttributeIndex, OWSE_Click);
    CHECK_ANCHOR_HANDLER(anchorOnMouseOverAttributeIndex, OWSE_MouseEntered);
    CHECK_ANCHOR_HANDLER(anchorOnMouseOutAttributeIndex, OWSE_MouseExited);
}
    
- (void)processEndAnchorTag:(OWSGMLTag *)tag;
{
    [self endAnchor];
}

- (void)endAnchor;
{
    [textBuilder endAnchor];
}

- (void)processBodyTag:(OWSGMLTag *)tag;
{
    NSString *background;
    OHColorPalette *oldColorPalette, *newColorPalette;
    NSString *handler;
    NSString *marginHeightString, *marginWidthString;

    if (flags.insideBodyTag)
	return; // Disallow nested BODY tags


    background = sgmlTagValueForAttributeAtIndex(tag, bodyBackgroundAttributeIndex);
    if (background)
        [[htmlDocument htmlPageView] setBackgroundImageAddress:[baseAddress addressForRelativeString:background]];

    // Event handlers
    handler = sgmlTagValueForAttributeAtIndex(tag, bodyOnLoadAttributeIndex);
    if (handler)
        [[self hintedScriptContext] setHandler:OWSE_Load
                                        onObject:[pipeline scriptProxy]
                                        toString:handler];
    handler = sgmlTagValueForAttributeAtIndex(tag, bodyOnUnloadAttributeIndex);
    if (handler)
        [[self hintedScriptContext] setHandler:OWSE_Unload
                                        onObject:[pipeline scriptProxy]
                                        toString:handler];

    oldColorPalette = [textBuilder colorPalette];
    newColorPalette = [oldColorPalette copyWithChangesFromTag:tag bgColorAttributeIndex:bodyBGColorAttributeIndex textAttributeIndex:bodyTextAttributeIndex linkAttributeIndex:bodyLinkAttributeIndex vlinkAttributeIndex:bodyVLinkAttributeIndex alinkAttributeIndex:bodyALinkAttributeIndex];
    [textBuilder setColorPalette:newColorPalette];
    [htmlDocument setColorPalette:newColorPalette]; // for scripts who want to know
    [newColorPalette release];

    // Margin height
    marginHeightString = sgmlTagValueForAttributeAtIndex(tag, bodyMarginHeightAttributeIndex);
    if (!marginHeightString) // Look for top margin attribute instead
        marginHeightString = sgmlTagValueForAttributeAtIndex(tag, bodyTopMarginAttributeIndex);

    // Margin width
    marginWidthString = sgmlTagValueForAttributeAtIndex(tag, bodyMarginWidthAttributeIndex);
    if (!marginWidthString) // Look for left margin attribute instead
        marginWidthString = sgmlTagValueForAttributeAtIndex(tag, bodyLeftMarginAttributeIndex);

    // See whether any of the margins changed
    if (marginHeightString || marginWidthString) {
        OHHTMLPageView *htmlPageView;
        NSSize marginSize;

        htmlPageView = [htmlDocument htmlPageView];
        marginSize = [htmlPageView marginSize];
        if (marginHeightString)
            marginSize.height = [marginHeightString intValue];
        if (marginWidthString)
            marginSize.width = [marginWidthString intValue];
        [htmlPageView setMarginSize:marginSize advisory:NO];
    }

    // flags.insideBodyTag = YES;
    // WJS 4/6/98: we used to call [self processContentForTag:tag], but that forces closing of other tags (notably tables) which Netscape 4.0 and IE 4.0 don't do.  We're seeing a lot of </BODY> in the middle of documents as servers do more stupid embedding.
}

- (void)processBreakTag:(OWSGMLTag *)tag;
{
    OHBreakType breakType;
    NSString *clear;

    [textBuilder addLineBreak];

    if (!sgmlTagAttributePresentAtIndex(tag, brClearAttributeIndex))
	return;
    clear = sgmlTagValueForAttributeAtIndex(tag, brClearAttributeIndex);

    switch ([clear firstCharacter]) {
        case 'l':
        case 'L':
            breakType = OHBreakClearLeftSide;
            break;
        case 'r':
        case 'R':
            breakType = OHBreakClearRightSide;
            break;
        default:
            breakType = OHBreakClearBothSides;
            break;
    }
    [textBuilder writeClearBreakObject:[OHBreak breakOfType:breakType]];
}

- (void)processHorizontalRuleTag:(OWSGMLTag *)tag;
{
    OWTextAlignment oldTextAlignment;
    BOOL oldUnderlined;
    NSString *source;
    OHBasicCell *genericRuleCell;

    oldTextAlignment = [textBuilder textAlignment];
    switch ([sgmlTagValueForAttributeAtIndex(tag, hrAlignAttributeIndex) firstCharacter]) {
        case 'l':
        case 'L':
            [textBuilder setTextAlignment:OW_LEFTALIGNED];
            break;
        case 'r':
        case 'R':
            [textBuilder setTextAlignment:OW_RIGHTALIGNED];
            break;
        default:
            [textBuilder setTextAlignment:OW_CENTERED];
            break;
    }
    
    [textBuilder ensureAtStartOfLine];
    oldUnderlined = [textBuilder setUnderlined:NO];
    
    if ((source = sgmlTagValueForAttributeAtIndex(tag, hrSrcAttributeIndex))) {
	OWAddress *sourceAddress;

	sourceAddress = [OHInlineImageCell addressForPossiblyInternalIconString:source];
        if (!sourceAddress)
	    sourceAddress = [baseAddress addressForRelativeString:source];
        genericRuleCell = [[OHInlineImageCell allocWithZone:[htmlDocument zone]] initWithAddress:sourceAddress referringAddress:baseAddress htmlDocument:htmlDocument];
    } else {
        genericRuleCell = [[OHHorizontalRule allocWithZone:[htmlDocument zone]] initWithNoShade:sgmlTagAttributePresentAtIndex(tag, hrNoshadeAttributeIndex)];

        // Size
        [genericRuleCell setRequestedSize:NSMakeSize(100.0, 2.0) requestedWidthIsPercentage:YES requestedHeightIsPercentage:NO]; // This sets the defaults
        [genericRuleCell setRequestedSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, hrWidthAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, hrSizeAttributeIndex)]; // Fills in user values
    }
    [textBuilder writeNonWhitespaceCellObject:genericRuleCell];
    [genericRuleCell release];
    
    [textBuilder setUnderlined:oldUnderlined];
    [textBuilder ensureAtStartOfLine];
    [textBuilder setTextAlignment:oldTextAlignment];
}

- (void)processHTMLTag:(OWSGMLTag *)tag;
{
    // WJS 4/6/98: we used to call [self processContentForTag:tag], but that forces closing of other tags (notably tables) which Netscape 4.0 and IE 4.0 don't do.  We're seeing a lot of </HTML> in the middle of documents as servers do more stupid embedding.
}

- (void)processImageTag:(OWSGMLTag *)tag;
{
    OHInlineImageCell *inlineImage;
    NSString *source, *border, *useMap;
    NSString *alternateText;
    NSString *name;
    NSString *handler;
    BOOL oldUnderlined;
    OWAddress *sourceAddress = nil;

    // Source address
    source = sgmlTagValueForAttributeAtIndex(tag, imgSrcAttributeIndex);
    if (source) {
        sourceAddress = [OHInlineImageCell addressForPossiblyInternalIconString:source];
        if (!sourceAddress)
            sourceAddress = [baseAddress addressForRelativeString:source];
    }

    inlineImage = [[OHInlineImageCell allocWithZone:[htmlDocument zone]] initWithAddress:sourceAddress referringAddress:baseAddress htmlDocument:htmlDocument];

    // Size
    [inlineImage setRequestedSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, imgWidthAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, imgHeightAttributeIndex)];

    // Alignment
    [inlineImage setAlignmentString:sgmlTagValueForAttributeAtIndex(tag, imgAlignAttributeIndex)];
    
    // Inset size
    [inlineImage setInsetSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, imgHSpaceAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, imgVSpaceAttributeIndex) defaultWidth:([inlineImage isMarginalCell] ? 3.0 : 0.0)];

    // Imagemap
    [inlineImage setIsMap:sgmlTagAttributePresentAtIndex(tag, imgIsMapAttributeIndex)];
    if ((useMap = sgmlTagValueForAttributeAtIndex(tag, imgUseMapAttributeIndex)))
	[inlineImage setMapAddress:[baseAddress addressForRelativeString:useMap inPipeline:pipeline target:nil effect:OWAddressEffectFollowInWindow]];

    // Set link
    [inlineImage setLink:[textBuilder anchor]];
    // Set Alternate text (don't call [textBuilder currentFont] unless necessary)
    if ((alternateText = sgmlTagValueForAttributeAtIndex(tag, imgAltAttributeIndex)))
        [inlineImage setAlternateText:alternateText inFont:[textBuilder currentFont]];

    // Border width
    if ((border = sgmlTagValueForAttributeAtIndex(tag, imgBorderAttributeIndex)))
	[inlineImage setBorderWidth:[border intValue]];

    // Name
    if ((name = sgmlTagValueForAttributeAtIndex(tag, imgNameAttributeIndex)))
        [inlineImage setName:name];

    // Event handlers
#define CHECK_IMAGE_HANDLER(tagindex, eventname) \
    if ((handler = sgmlTagValueForAttributeAtIndex(tag, tagindex))) \
        [[self hintedScriptContext] setHandler:eventname onObject:inlineImage toString:handler];
    CHECK_IMAGE_HANDLER(imgOnLoadAttributeIndex, OWSE_Load);
    CHECK_IMAGE_HANDLER(imgOnAbortAttributeIndex, OWSE_Abort);
    CHECK_IMAGE_HANDLER(imgOnErrorAttributeIndex, OWSE_Error);

    // Add to text (underlines off)
    oldUnderlined = [textBuilder setUnderlined:NO];
    [textBuilder writeNonWhitespaceCellObject:inlineImage];
    [textBuilder setUnderlined:oldUnderlined];

    // Add to the OHHTMLDocument, which keeps a list of all images in the document
    [htmlDocument addImage:inlineImage];

    // Release the image
    [inlineImage release];
}

- (void)processLinkTag:(OWSGMLTag *)tag;
{
}

- (void)processSpacerTag:(OWSGMLTag *)tag;
{
    OHSpacerCell *spacer;
    NSString *type;
    BOOL oldUnderlined;

    spacer = [[OHSpacerCell allocWithZone:[htmlDocument zone]] init];
    type = sgmlTagValueForAttributeAtIndex(tag, spacerTypeAttributeIndex);
    switch ([type firstCharacter]) {
        case 'v':
        case 'V':
            //Vertical

            // Size
            [spacer setRequestedSizeWidthString:nil heightString:sgmlTagValueForAttributeAtIndex(tag, spacerSizeAttributeIndex)];
            break;
        case 'b':
        case 'B':
            //Block Spacer

            // Size
            [spacer setRequestedSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, spacerWidthAttributeIndex) heightString:sgmlTagValueForAttributeAtIndex(tag, spacerHeightAttributeIndex)];

            // Alignment
            [spacer setAlignmentString:sgmlTagValueForAttributeAtIndex(tag, spacerAlignAttributeIndex)];
            break;
        default:
            //Horizontal
            [spacer setRequestedSizeWidthString:sgmlTagValueForAttributeAtIndex(tag, spacerSizeAttributeIndex) heightString:nil];
            break;
    }

    // Add to text (underlines off)
    oldUnderlined = [textBuilder setUnderlined:NO];
    [textBuilder writeNonWhitespaceCellObject:spacer];
    [textBuilder setUnderlined:oldUnderlined];
}


@end
