gecko-dev/content/html/document/src/nsHTMLContentSink.cpp

1349 lines
36 KiB
C++
Raw Normal View History

1998-04-13 20:24:54 +00:00
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsIHTMLContentSink.h"
#include "nsIStyleSheet.h"
#include "nsIUnicharInputStream.h"
#include "nsIHTMLContent.h"
#include "nsIURL.h"
#include "nsHTMLDocument.h"
#include "nsIPresShell.h"
#include "nsIPresContext.h"
#include "nsHTMLTokens.h"
#include "nsCRT.h"
#include "prtime.h"
#include "prlog.h"
#include "nsHTMLParts.h"
#include "nsTablePart.h"
#include "nsTableRow.h"
#include "nsTableCell.h"
#include "nsHTMLForms.h"
#include "nsIFormManager.h"
#include "nsIFormControl.h"
1998-04-13 20:24:54 +00:00
#include "nsIImageMap.h"
#include "nsRepository.h"
1998-04-13 20:24:54 +00:00
// XXX attribute values have entities in them - use the parsers expander!
// XXX Go through a factory for this one
#include "nsICSSParser.h"
#ifdef NS_DEBUG
static PRInt32 gLogLevel = 0;
#define NOISY_SINK_TRACE(_msg,_node) \
{ \
char buf[200]; \
(_node).GetText().ToCString(buf, sizeof(buf)); \
if (gLogLevel >= PR_LOG_WARNING) { \
PR_LogPrint("%s; [%s]", _msg, buf); \
} \
}
#define REALLY_NOISY_SINK_TRACE(_msg,_node) \
{ \
char buf[200]; \
(_node).GetText().ToCString(buf, sizeof(buf)); \
if (gLogLevel >= PR_LOG_DEBUG) { \
PR_LogPrint("%s; [%s]", _msg, buf); \
} \
}
#else /* !NOISY_SINK */
#define NOISY_SINK_TRACE(_a,_node)
#define REALLY_NOISY_SINK_TRACE(_a,_node)
#endif /* NOISY_SINK */
//----------------------------------------------------------------------
static NS_DEFINE_IID(kIHTMLContentSinkIID, NS_IHTMLCONTENTSINK_IID);
static NS_DEFINE_IID(kIFormControlIID, NS_IFORMCONTROL_IID);
1998-04-13 20:24:54 +00:00
class HTMLContentSink : public nsIHTMLContentSink {
public:
NS_DECL_ISUPPORTS
HTMLContentSink();
~HTMLContentSink();
void* operator new(size_t size) {
void* rv = ::operator new(size);
nsCRT::zero(rv, size);
return (void*) rv;
}
nsresult Init(nsIDocument* aDoc, nsIURL* aURL);
nsIHTMLContent* GetCurrentContainer(eHTMLTags* aType);
virtual PRBool SetTitle(const nsString& aValue);
// Called when Opening or closing the main HTML container
virtual PRBool OpenHTML(const nsIParserNode& aNode);
virtual PRBool CloseHTML(const nsIParserNode& aNode);
// Called when Opening or closing the main HEAD container
virtual PRBool OpenHead(const nsIParserNode& aNode);
virtual PRBool CloseHead(const nsIParserNode& aNode);
// Called when Opening or closing the main BODY container
virtual PRBool OpenBody(const nsIParserNode& aNode);
virtual PRBool CloseBody(const nsIParserNode& aNode);
// Called when Opening or closing FORM containers
virtual PRBool OpenForm(const nsIParserNode& aNode);
virtual PRBool CloseForm(const nsIParserNode& aNode);
// Called when Opening or closing the main FRAMESET container
virtual PRBool OpenFrameset(const nsIParserNode& aNode);
virtual PRBool CloseFrameset(const nsIParserNode& aNode);
// Called when Opening or closing a general container
// This includes: OL,UL,DIR,SPAN,TABLE,H[1..6],etc.
// Until proven otherwise, I also plan to toss STYLE,
// FORMS, FRAME, SCRIPT, etc. here too!
virtual PRBool OpenContainer(const nsIParserNode& aNode);
virtual PRBool CloseContainer(const nsIParserNode& aNode);
virtual PRBool CloseTopmostContainer();
// Called for text, comments and so on...
virtual PRBool AddLeaf(const nsIParserNode& aNode);
1998-05-07 21:11:07 +00:00
virtual void WillBuildModel(void);
virtual void DidBuildModel(void);
virtual void WillInterrupt(void);
virtual void WillResume(void);
1998-04-13 20:24:54 +00:00
protected:
void StartLayout();
void ReflowNewContent();
//----------------------------------------------------------------------
// Leaf tag handler routines that translate a leaf tag into a
// content object, processing all of the tag attributes.
nsresult ProcessAREATag(const nsIParserNode& aNode);
nsresult ProcessBASETag(const nsIParserNode& aNode);
nsresult ProcessSTYLETag(const nsIParserNode& aNode);
nsresult ProcessBRTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode);
nsresult ProcessHRTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode);
nsresult ProcessINPUTTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode);
nsresult ProcessIMGTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode);
nsresult ProcessSPACERTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode);
nsresult ProcessTEXTAREATag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode);
1998-04-13 20:24:54 +00:00
nsresult ProcessWBRTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode);
//----------------------------------------------------------------------
1998-04-22 23:24:43 +00:00
nsresult ProcessOpenSELECTTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode);
nsresult ProcessCloseSELECTTag(const nsIParserNode& aNode);
1998-05-08 23:08:43 +00:00
nsresult ProcessOpenOPTIONTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode);
1998-04-22 23:24:43 +00:00
nsresult ProcessCloseOPTIONTag(const nsIParserNode& aNode);
nsresult ProcessOPTIONTagContent(const nsIParserNode& aNode);
//----------------------------------------------------------------------
1998-04-13 20:24:54 +00:00
void GetAttributeValueAt(const nsIParserNode& aNode,
PRInt32 aIndex,
nsString& aResult);
PRBool FindAttribute(const nsIParserNode& aNode,
const nsString& aKeyName,
nsString& aResult);
1998-04-13 20:24:54 +00:00
nsresult AddAttributes(const nsIParserNode& aNode,
nsIHTMLContent* aInstancePtrResult);
nsresult LoadStyleSheet(nsIURL* aURL,
nsIUnicharInputStream* aUIN);
nsIDocument* mDocument;
nsIURL* mDocumentURL;
eHTMLTags mNodeStack[100];/* XXX */
nsIHTMLContent* mContainerStack[100];/* XXX */
PRInt32 mStackPos;
nsString* mTitle;
nsString mBaseHREF;
nsString mBaseTarget;
nsIStyleSheet* mStyleSheet;
nsIFormManager* mCurrentForm;
nsIImageMap* mCurrentMap;
nsIHTMLContent* mCurrentSelect;
nsIHTMLContent* mCurrentOption;
1998-04-13 20:24:54 +00:00
nsIHTMLContent* mRoot;
nsIHTMLContent* mBody;
PRTime mLastUpdateTime;
PRTime mUpdateDelta;
PRBool mLayoutStarted;
};
// Note: operator new zeros our memory
HTMLContentSink::HTMLContentSink()
{
#if 0
if (nsnull == gSinkLog) {
gSinkLog = PR_NewLogModule("HTMLContentSink");
}
#endif
// Set the first update delta to be 50ms
LL_I2L(mUpdateDelta, PR_USEC_PER_MSEC * 50);
}
HTMLContentSink::~HTMLContentSink()
{
NS_IF_RELEASE(mBody);
NS_IF_RELEASE(mRoot);
1998-04-16 21:49:49 +00:00
NS_IF_RELEASE(mDocument);
1998-04-13 20:24:54 +00:00
NS_IF_RELEASE(mDocumentURL);
NS_IF_RELEASE(mStyleSheet);
NS_IF_RELEASE(mCurrentForm);
NS_IF_RELEASE(mCurrentMap);
1998-04-22 23:24:43 +00:00
NS_IF_RELEASE(mCurrentSelect);
NS_IF_RELEASE(mCurrentOption);
1998-04-13 20:24:54 +00:00
if (nsnull != mTitle) {
delete mTitle;
}
}
nsresult HTMLContentSink::Init(nsIDocument* aDoc, nsIURL* aDocURL)
{
1998-04-16 21:49:49 +00:00
NS_IF_RELEASE(mDocument);
1998-04-13 20:24:54 +00:00
mDocument = aDoc;
1998-04-16 21:49:49 +00:00
NS_IF_ADDREF(mDocument);
1998-04-13 20:24:54 +00:00
1998-04-16 21:49:49 +00:00
NS_IF_RELEASE(mDocumentURL);
1998-04-13 20:24:54 +00:00
mDocumentURL = aDocURL;
1998-04-16 21:49:49 +00:00
NS_IF_ADDREF(mDocumentURL);
1998-04-13 20:24:54 +00:00
// Make root part
1998-04-16 21:49:49 +00:00
NS_IF_RELEASE(mRoot);
1998-04-13 20:24:54 +00:00
nsresult rv = NS_NewRootPart(&mRoot, aDoc);
if (NS_OK == rv) {
}
return rv;
}
NS_IMPL_ISUPPORTS(HTMLContentSink,kIHTMLContentSinkIID)
PRBool HTMLContentSink::OpenHTML(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("OpenHTML", aNode)
NS_PRECONDITION(0 == mStackPos, "bad stack pos");
mNodeStack[0] = (eHTMLTags)aNode.GetNodeType();
mContainerStack[0] = mRoot;
mStackPos = 1;
return PR_TRUE;
}
PRBool HTMLContentSink::CloseHTML(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("CloseHTML", aNode)
NS_ASSERTION(mStackPos > 0, "bad bad");
mNodeStack[--mStackPos] = eHTMLTag_unknown;
PRInt32 i, ns = mDocument->GetNumberOfShells();
for (i = 0; i < ns; i++) {
nsIPresShell* shell = mDocument->GetShellAt(i);
if (nsnull != shell) {
shell->BeginObservingDocument();
NS_RELEASE(shell);
}
}
NS_IF_RELEASE(mCurrentForm);
1998-04-25 19:46:11 +00:00
return PR_TRUE;
1998-04-13 20:24:54 +00:00
}
PRBool HTMLContentSink::OpenHead(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("OpenHead", aNode)
mNodeStack[mStackPos++] = (eHTMLTags)aNode.GetNodeType();
return PR_TRUE;
}
PRBool HTMLContentSink::CloseHead(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("CloseHead", aNode)
NS_ASSERTION(mStackPos > 0, "bad bad");
mNodeStack[--mStackPos] = eHTMLTag_unknown;
return PR_TRUE;
}
PRBool HTMLContentSink::SetTitle(const nsString& aValue)
{
if (nsnull == mTitle) {
mTitle = new nsString(aValue);
}
else {
*mTitle = aValue;
}
mTitle->CompressWhitespace(PR_TRUE, PR_TRUE);
((nsHTMLDocument*)mDocument)->SetTitle(*mTitle);
return PR_TRUE;
}
PRBool HTMLContentSink::OpenBody(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("OpenBody", aNode)
PRBool startLayout = PR_FALSE;
if (nsnull == mBody) {
nsIAtom* atom = NS_NewAtom("BODY");
nsresult rv = NS_NewBodyPart(&mBody, atom);
if (NS_OK == rv) {
mRoot->AppendChild(mBody);
startLayout = PR_TRUE;
}
NS_RELEASE(atom);
}
mNodeStack[mStackPos] = (eHTMLTags)aNode.GetNodeType();
mContainerStack[mStackPos] = mBody;
mStackPos++;
// Add attributes to the body content object, but only if it's really a body
// tag that is triggering the OpenBody.
if (aNode.GetText().EqualsIgnoreCase("body")) {
AddAttributes(aNode, mBody);
// XXX If the body already existed and has been reflowed somewhat
// then we need to trigger a style change
}
if (startLayout) {
// XXX This has to be done now that the body is in because we
// don't know how to handle a content-appended reflow if the
// root has no children
StartLayout();
}
return PR_TRUE;
}
PRBool HTMLContentSink::CloseBody(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("CloseBody", aNode)
NS_ASSERTION(mStackPos > 0, "bad bad");
mNodeStack[--mStackPos] = eHTMLTag_unknown;
// Reflow any lingering content
ReflowNewContent();
return PR_TRUE;
}
static NS_DEFINE_IID(kIFormManagerIID, NS_IFORMMANAGER_IID);
1998-04-13 20:24:54 +00:00
PRBool HTMLContentSink::OpenForm(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("OpenForm", aNode)
// Close out previous form if it's there
if (nsnull != mCurrentForm) {
NS_RELEASE(mCurrentForm);
mCurrentForm = nsnull;
}
nsresult rv;
nsAutoString classID;
if (FindAttribute(aNode, "classid", classID)) {
// Translate classid string into an nsID
char cbuf[50];
classID.ToCString(cbuf, sizeof(cbuf));
nsID clid;
if (clid.Parse(cbuf)) {
// Create a form manager using the repository
nsIFactory* fac;
rv = NSRepository::FindFactory(clid, &fac);
if (NS_OK == rv) {
rv = fac->CreateInstance(nsnull, kIFormManagerIID,
(void**)&mCurrentForm);
#ifdef NS_DEBUG
if (NS_OK != rv) {
printf("OpenForm: can't create form manager instance: %d\n", rv);
}
#endif
NS_RELEASE(fac);
}
else {
#ifdef NS_DEBUG
printf("OpenForm: can't find '%s' in the repository\n", cbuf);
#endif
}
}
else {
#ifdef NS_DEBUG
printf("OpenForm: classID is invalid: '%s'\n", cbuf);
#endif
}
}
if (nsnull == mCurrentForm) {
// Create new form
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
rv = NS_NewHTMLForm(&mCurrentForm, atom);
NS_RELEASE(atom);
}
1998-04-13 20:24:54 +00:00
if (NS_OK == rv) {
// Add tag attributes to the form
nsAutoString k, v;
PRInt32 ac = aNode.GetAttributeCount();
for (PRInt32 i = 0; i < ac; i++) {
// Get upper-cased key
const nsString& key = aNode.GetKeyAt(i);
k.SetLength(0);
k.Append(key);
k.ToUpperCase();
// Get value and remove mandatory quotes
GetAttributeValueAt(aNode, i, v);
mCurrentForm->SetAttribute(k, v);
}
}
return PR_TRUE;
}
PRBool HTMLContentSink::CloseForm(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("CloseForm", aNode)
if (nsnull != mCurrentForm) {
NS_RELEASE(mCurrentForm);
mCurrentForm = nsnull;
}
return PR_TRUE;
}
PRBool HTMLContentSink::OpenFrameset(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("OpenFrameset", aNode)
mNodeStack[mStackPos++] = (eHTMLTags)aNode.GetNodeType();
return PR_TRUE;
}
PRBool HTMLContentSink::CloseFrameset(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("CloseFrameset", aNode)
mNodeStack[--mStackPos] = eHTMLTag_unknown;
return PR_TRUE;
}
PRBool HTMLContentSink::OpenContainer(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("OpenContainer", aNode)
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
1998-04-22 23:24:43 +00:00
eHTMLTags parentType;
nsIHTMLContent* parent = GetCurrentContainer(&parentType);
switch (parentType) {
case eHTMLTag_select:
break;
case eHTMLTag_option:
break;
}
1998-04-13 20:24:54 +00:00
nsresult rv;
nsIHTMLContent* container = nsnull;
if (aNode.GetTokenType() == eToken_start) {
switch (aNode.GetNodeType()) {
case eHTMLTag_map:
NS_IF_RELEASE(mCurrentMap);
rv = NS_NewImageMap(&mCurrentMap, atom);
if (NS_OK == rv) {
// Look for name attribute and set the map name
nsAutoString name;
if (FindAttribute(aNode, "name", name)) {
name.StripWhitespace(); // XXX leading, trailing, interior non=-space ws is removed
mCurrentMap->SetName(name);
1998-04-13 20:24:54 +00:00
}
// Add the map to the document
((nsHTMLDocument*)mDocument)->AddImageMap(mCurrentMap);
}
return PR_TRUE;
case eHTMLTag_table:
rv = NS_NewTablePart(&container, atom);
break;
case eHTMLTag_caption:
rv = NS_NewTableCaptionPart(&container, atom);
break;
case eHTMLTag_tr:
rv = NS_NewTableRowPart(&container, atom);
break;
case eHTMLTag_tbody:
case eHTMLTag_thead:
case eHTMLTag_tfoot:
rv = NS_NewTableRowGroupPart(&container, atom);
break;
case eHTMLTag_colgroup:
rv = NS_NewTableColGroupPart(&container, atom);
break;
case eHTMLTag_col:
rv = NS_NewTableColPart(&container, atom);
break;
case eHTMLTag_td:
case eHTMLTag_th:
rv = NS_NewTableCellPart(&container, atom);
break;
1998-04-22 23:24:43 +00:00
case eHTMLTag_select:
rv = ProcessOpenSELECTTag(&container, aNode);
break;
case eHTMLTag_option:
1998-05-08 23:08:43 +00:00
rv = ProcessOpenOPTIONTag(&container, aNode);
1998-04-22 23:24:43 +00:00
break;
1998-04-13 20:24:54 +00:00
default:
rv = NS_NewHTMLContainer(&container, atom);
break;
}
}
// XXX for now assume that if it's a container, it's a simple container
mNodeStack[mStackPos] = (eHTMLTags) aNode.GetNodeType();
mContainerStack[mStackPos] = container;
if (nsnull != container) {
container->SetDocument(mDocument);
rv = AddAttributes(aNode, container);
mStackPos++;
}
NS_RELEASE(atom);
return PR_TRUE;
}
PRBool HTMLContentSink::CloseContainer(const nsIParserNode& aNode)
{
NOISY_SINK_TRACE("CloseContainer", aNode)
switch (aNode.GetNodeType()) {
case eHTMLTag_map:
NS_IF_RELEASE(mCurrentMap);
return PR_TRUE;
}
// XXX we could assert things about the top tag name == aNode.getText
if (0 == mStackPos) {
// Can't pop empty stack
return PR_TRUE;
}
--mStackPos;
nsIHTMLContent* container = mContainerStack[mStackPos];
mNodeStack[mStackPos] = eHTMLTag_unknown;
mContainerStack[mStackPos] = nsnull;
1998-04-22 23:24:43 +00:00
switch (aNode.GetNodeType()) {
case eHTMLTag_option:
ProcessCloseOPTIONTag(aNode);
break;
case eHTMLTag_select:
ProcessCloseSELECTTag(aNode);
break;
default:
if (nsnull != container) {
// Now that this container is complete, append it to it's parent
eHTMLTags parentType;
nsIHTMLContent* parent = GetCurrentContainer(&parentType);
container->Compact();
if(parent) {
parent->AppendChild(container);
if (parent == mBody) {
// We just closed a child of the body off. Trigger a
// content-appended reflow if enough time has elapsed
PRTime now = PR_Now();
if (now - mLastUpdateTime >= mUpdateDelta) {
mLastUpdateTime = now;
mUpdateDelta += mUpdateDelta;
ReflowNewContent();
}
1998-04-13 20:24:54 +00:00
}
}
1998-04-22 23:24:43 +00:00
NS_RELEASE(container);
1998-04-13 20:24:54 +00:00
}
1998-04-22 23:24:43 +00:00
break;
1998-04-13 20:24:54 +00:00
}
1998-04-22 23:24:43 +00:00
1998-04-13 20:24:54 +00:00
return PR_TRUE;
}
void HTMLContentSink::StartLayout()
{
if (!mLayoutStarted) {
PRInt32 i, ns = mDocument->GetNumberOfShells();
for (i = 0; i < ns; i++) {
nsIPresShell* shell = mDocument->GetShellAt(i);
if (nsnull != shell) {
nsIPresContext* cx = shell->GetPresContext();
nsRect r = cx->GetVisibleArea();
shell->ResizeReflow(r.width, r.height);
NS_RELEASE(cx);
NS_RELEASE(shell);
}
}
mLayoutStarted = PR_TRUE;
}
}
void HTMLContentSink::ReflowNewContent()
{
printf("reflow body\n");
// Trigger reflows in each of the presentation shells
PRInt32 i, ns = mDocument->GetNumberOfShells();
for (i = 0; i < ns; i++) {
nsIPresShell* shell = mDocument->GetShellAt(i);
if (nsnull != shell) {
shell->ContentAppended(mBody);
NS_RELEASE(shell);
}
}
}
PRBool HTMLContentSink::CloseTopmostContainer()
{
return PR_TRUE;
}
nsIHTMLContent* HTMLContentSink::GetCurrentContainer(eHTMLTags* aType)
{
nsIHTMLContent* parent;
if (mStackPos <= 2) { // assume HTML and BODY are on the stack
parent = mBody;
*aType = eHTMLTag_body;
} else {
parent = mContainerStack[mStackPos - 1];
*aType = mNodeStack[mStackPos - 1];
}
return parent;
}
//----------------------------------------------------------------------
// Leaf tag handling code
PRBool HTMLContentSink::AddLeaf(const nsIParserNode& aNode)
{
REALLY_NOISY_SINK_TRACE("AddLeaf", aNode)
// Check for nodes that require special handling
switch (aNode.GetNodeType()) {
case eHTMLTag_style:
ProcessSTYLETag(aNode);
return PR_TRUE;
case eHTMLTag_script:
return PR_TRUE;
case eHTMLTag_area:
ProcessAREATag(aNode);
return PR_TRUE;
}
eHTMLTags parentType;
nsIHTMLContent* parent = GetCurrentContainer(&parentType);
switch (parentType) {
/*case eHTMLTag_table:
1998-04-13 20:24:54 +00:00
case eHTMLTag_tr:
1998-04-22 23:24:43 +00:00
case eHTMLTag_tbody:
1998-04-21 16:36:46 +00:00
case eHTMLTag_thead:
case eHTMLTag_tfoot:
1998-04-13 20:24:54 +00:00
// XXX Discard leaf content (those annoying \n's really) in
// table's or table rows
return PR_TRUE;
*/
1998-04-22 23:24:43 +00:00
case eHTMLTag_option:
ProcessOPTIONTagContent(aNode);
return PR_TRUE;
case eHTMLTag_select:
// Discard content in a select that's not an option
if (eHTMLTag_option != aNode.GetNodeType()) {
return PR_TRUE;
}
break;
1998-04-13 20:24:54 +00:00
}
1998-04-13 20:24:54 +00:00
nsresult rv = NS_OK;
nsIHTMLContent* leaf = nsnull;
switch (aNode.GetTokenType()) {
case eToken_start:
switch (aNode.GetNodeType()) {
case eHTMLTag_br:
rv = ProcessBRTag(&leaf, aNode);
break;
case eHTMLTag_hr:
rv = ProcessHRTag(&leaf, aNode);
break;
case eHTMLTag_input:
rv = ProcessINPUTTag(&leaf, aNode);
break;
case eHTMLTag_img:
rv = ProcessIMGTag(&leaf, aNode);
break;
case eHTMLTag_spacer:
rv = ProcessSPACERTag(&leaf, aNode);
break;
case eHTMLTag_textarea:
ProcessTEXTAREATag(&leaf, aNode);
break;
1998-04-13 20:24:54 +00:00
}
break;
case eToken_text:
case eToken_whitespace:
{
nsAutoString tmp;
tmp.Append(aNode.GetText());
rv = NS_NewHTMLText(&leaf, tmp.GetUnicode(), tmp.Length());
}
break;
// XXX ick
case eToken_newline:
{
nsAutoString tmp;
tmp.Append('\n');
rv = NS_NewHTMLText(&leaf, tmp.GetUnicode(), tmp.Length());
}
break;
case eToken_entity:
{
nsAutoString tmp;
nsAutoString tmp2("");
PRInt32 unicode = aNode.TranslateToUnicodeStr(tmp2);
1998-04-13 20:24:54 +00:00
if (unicode < 0) {
tmp.Append(aNode.GetText());
} else {
tmp+=tmp2;
1998-04-13 20:24:54 +00:00
}
rv = NS_NewHTMLText(&leaf, tmp.GetUnicode(), tmp.Length());
}
break;
case eToken_skippedcontent:
break;
}
if (NS_OK == rv) {
if (nsnull != leaf) {
if (nsnull != parent) {
parent->AppendChild(leaf);
} else {
// XXX drop stuff on the floor that doesn't have a container!
// Bad parser!
}
}
}
NS_IF_RELEASE(leaf);
return PR_TRUE;
}
void HTMLContentSink::GetAttributeValueAt(const nsIParserNode& aNode,
PRInt32 aIndex,
nsString& aResult)
{
// Copy value
const nsString& value = aNode.GetValueAt(aIndex);
aResult.Truncate();
aResult.Append(value);
// strip quotes if present
PRUnichar first = aResult.First();
if ((first == '"') || (first == '\'')) {
if (aResult.Last() == first) {
aResult.Cut(0, 1);
PRInt32 pos = aResult.Length() - 1;
if (pos >= 0) {
aResult.Cut(pos, 1);
}
} else {
// Mismatched quotes - leave them in
}
}
}
PRBool HTMLContentSink::FindAttribute(const nsIParserNode& aNode,
const nsString& aKeyName,
nsString& aResult)
{
PRInt32 ac = aNode.GetAttributeCount();
for (PRInt32 i = 0; i < ac; i++) {
const nsString& key = aNode.GetKeyAt(i);
if (key.EqualsIgnoreCase(aKeyName)) {
// Get value and remove mandatory quotes
GetAttributeValueAt(aNode, i, aResult);
return PR_TRUE;
}
}
return PR_FALSE;
}
1998-04-13 20:24:54 +00:00
nsresult HTMLContentSink::AddAttributes(const nsIParserNode& aNode,
nsIHTMLContent* aInstancePtrResult)
{
nsIContent* content = (nsIContent*) aInstancePtrResult;
// Add tag attributes to the content attributes
nsAutoString k, v;
PRInt32 ac = aNode.GetAttributeCount();
for (PRInt32 i = 0; i < ac; i++) {
// Get upper-cased key
const nsString& key = aNode.GetKeyAt(i);
k.Truncate();
k.Append(key);
k.ToUpperCase();
// Get value and remove mandatory quotes
GetAttributeValueAt(aNode, i, v);
content->SetAttribute(k, v);
}
return NS_OK;
}
nsresult HTMLContentSink::ProcessAREATag(const nsIParserNode& aNode)
{
if (nsnull != mCurrentMap) {
nsAutoString shape, coords, href, target(mBaseTarget), alt;
PRInt32 ac = aNode.GetAttributeCount();
PRBool suppress = PR_FALSE;
for (PRInt32 i = 0; i < ac; i++) {
// Get upper-cased key
const nsString& key = aNode.GetKeyAt(i);
if (key.EqualsIgnoreCase("shape")) {
GetAttributeValueAt(aNode, i, shape);
}
else if (key.EqualsIgnoreCase("coords")) {
GetAttributeValueAt(aNode, i, coords);
}
else if (key.EqualsIgnoreCase("href")) {
GetAttributeValueAt(aNode, i, href);
href.StripWhitespace();
}
else if (key.EqualsIgnoreCase("target")) {
GetAttributeValueAt(aNode, i, target);
}
else if (key.EqualsIgnoreCase("alt")) {
GetAttributeValueAt(aNode, i, alt);
}
else if (key.EqualsIgnoreCase("suppress")) {
suppress = PR_TRUE;
}
}
mCurrentMap->AddArea(mBaseHREF, shape, coords, href, target, alt,
suppress);
}
return NS_OK;
}
nsresult HTMLContentSink::ProcessBASETag(const nsIParserNode& aNode)
{
PRInt32 ac = aNode.GetAttributeCount();
for (PRInt32 i = 0; i < ac; i++) {
const nsString& key = aNode.GetKeyAt(i);
if (key.EqualsIgnoreCase("href")) {
const nsString& href = aNode.GetValueAt(i);
if (href.Length() > 0) {
mBaseHREF = href;
}
} else if (key.EqualsIgnoreCase("target")) {
const nsString& target= aNode.GetValueAt(i);
if (target.Length() > 0) {
mBaseTarget = target;
}
}
}
return NS_OK;
}
nsresult HTMLContentSink::ProcessBRTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode)
{
nsresult rv = NS_OK;
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
rv = NS_NewHTMLBreak(aInstancePtrResult, atom);
if (NS_OK == rv) {
rv = AddAttributes(aNode, *aInstancePtrResult);
}
NS_RELEASE(atom);
return rv;
}
nsresult HTMLContentSink::ProcessHRTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode)
{
nsresult rv = NS_OK;
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
rv = NS_NewHRulePart(aInstancePtrResult, atom);
if (NS_OK == rv) {
rv = AddAttributes(aNode, *aInstancePtrResult);
}
NS_RELEASE(atom);
return rv;
}
nsresult HTMLContentSink::ProcessIMGTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode)
{
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
nsresult rv = NS_NewHTMLImage(aInstancePtrResult, atom);
if (NS_OK == rv) {
rv = AddAttributes(aNode, *aInstancePtrResult);
// XXX get base url to image
}
NS_RELEASE(atom);
return rv;
}
nsresult HTMLContentSink::ProcessSPACERTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode)
{
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
nsresult rv = NS_NewHTMLSpacer(aInstancePtrResult, atom);
if (NS_OK == rv) {
rv = AddAttributes(aNode, *aInstancePtrResult);
}
NS_RELEASE(atom);
return rv;
}
// 3 ways to load a style sheet: inline, style src=, link tag
// XXX What does nav do if we have SRC= and some style data inline?
nsresult HTMLContentSink::ProcessSTYLETag(const nsIParserNode& aNode)
{
nsresult rv = NS_OK;
PRInt32 i, ac = aNode.GetAttributeCount();
nsString* src = nsnull;
for (i = 0; i < ac; i++) {
const nsString& key = aNode.GetKeyAt(i);
if (key.EqualsIgnoreCase("src")) {
src = new nsString(aNode.GetValueAt(i));
}
}
// The skipped content contains the inline style data
const nsString& content = aNode.GetSkippedContent();
nsIURL* url = nsnull;
nsIUnicharInputStream* uin = nsnull;
if (nsnull == src) {
// Create a string to hold the data and wrap it up in a unicode
// input stream.
rv = NS_NewStringUnicharInputStream(&uin, new nsString(content));
if (NS_OK != rv) {
return rv;
}
// Use the document's url since the style data came from there
url = mDocumentURL;
NS_IF_ADDREF(url);
} else {
// src with immediate style data doesn't add up
// XXX what does nav do?
char* spec = src->ToNewCString();
rv = NS_NewURL(&url, nsnull, spec);
delete spec;
delete src;
if (NS_OK != rv) {
return rv;
}
PRInt32 ec;
nsIInputStream* iin = url->Open(&ec);
if (nsnull == iin) {
NS_RELEASE(url);
return (nsresult) ec;/* XXX fix url->Open */
}
rv = NS_NewConverterStream(&uin, nsnull, iin);
NS_RELEASE(iin);
if (NS_OK != rv) {
NS_RELEASE(url);
return rv;
}
}
// Now that we have a url and a unicode input stream, parse the
// style sheet.
rv = LoadStyleSheet(url, uin);
NS_RELEASE(uin);
NS_RELEASE(url);
return rv;
}
nsresult HTMLContentSink::LoadStyleSheet(nsIURL* aURL,
nsIUnicharInputStream* aUIN)
{
/* XXX use repository */
nsICSSParser* parser;
nsresult rv = NS_NewCSSParser(&parser);
if (NS_OK == rv) {
PRInt32 ec;
if (nsnull != mStyleSheet) {
parser->SetStyleSheet(mStyleSheet);
// XXX we do probably need to trigger a style change reflow
// when we are finished if this is adding data to the same sheet
}
nsIStyleSheet* sheet = parser->Parse(&ec, aUIN, mDocumentURL);
if (nsnull != sheet) {
if (nsnull == mStyleSheet) {
// Add in the sheet the first time; if we update the sheet
// with new data (mutliple style tags in the same document)
// then the sheet will be updated by the css parser and
// therefore we don't need to add it to the document)
mDocument->AddStyleSheet(sheet);
mStyleSheet = sheet;
}
rv = NS_OK;
} else {
rv = NS_ERROR_OUT_OF_MEMORY;/* XXX */
}
NS_RELEASE(parser);
}
return rv;
}
nsresult HTMLContentSink::ProcessINPUTTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode)
{
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
nsresult rv = NS_ERROR_NOT_INITIALIZED;
// Find type attribute and then create the appropriate form element
nsAutoString val;
if (FindAttribute(aNode, "type", val)) {
if (val.EqualsIgnoreCase("submit")) {
rv = NS_NewHTMLInputSubmit(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("reset")) {
rv = NS_NewHTMLInputReset(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("button")) {
rv = NS_NewHTMLInputButton(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("checkbox")) {
rv = NS_NewHTMLInputCheckbox(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("file")) {
rv = NS_NewHTMLInputFile(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("hidden")) {
rv = NS_NewHTMLInputHidden(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("image")) {
rv = NS_NewHTMLInputImage(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("password")) {
rv = NS_NewHTMLInputPassword(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("radio")) {
rv = NS_NewHTMLInputRadio(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("text")) {
rv = NS_NewHTMLInputText(aInstancePtrResult, atom, mCurrentForm);
}
else if (val.EqualsIgnoreCase("select1")) { // TEMP hack
rv = NS_NewHTMLSelect(aInstancePtrResult, atom, mCurrentForm, 1);
}
else if (val.EqualsIgnoreCase("select2")) { // TEMP hack
rv = NS_NewHTMLSelect(aInstancePtrResult, atom, mCurrentForm, 2);
}
else if (val.EqualsIgnoreCase("select3")) { // TEMP hack
rv = NS_NewHTMLSelect(aInstancePtrResult, atom, mCurrentForm, 3);
}
else {
rv = NS_NewHTMLInputSubmit(aInstancePtrResult, atom, mCurrentForm);
1998-04-13 20:24:54 +00:00
}
}
if (NS_ERROR_NOT_INITIALIZED == rv) {
// Create textfield when no type is specified
rv = NS_NewHTMLInputText(aInstancePtrResult, atom, mCurrentForm);
}
if ((NS_OK == rv) && (nsnull != *aInstancePtrResult)) {
// Add remaining attributes from the tag
rv = AddAttributes(aNode, *aInstancePtrResult);
}
NS_RELEASE(atom);
return rv;
}
nsresult
HTMLContentSink::ProcessTEXTAREATag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode)
{
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
const nsString& content = aNode.GetSkippedContent();
nsresult rv = NS_NewHTMLTextArea(aInstancePtrResult, atom, mCurrentForm);
if ((NS_OK == rv) && (nsnull != *aInstancePtrResult)) {
// Add remaining attributes from the tag
rv = AddAttributes(aNode, *aInstancePtrResult);
if (0 < content.Length()) {
nsIFormControl* control;
rv = (*aInstancePtrResult)->QueryInterface(kIFormControlIID, (void **)&control);
if (NS_OK == rv) {
control->SetContent(content);
}
}
}
NS_RELEASE(atom);
return rv;
}
1998-04-22 23:24:43 +00:00
nsresult
HTMLContentSink::ProcessOpenSELECTTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode)
{
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
if (nsnull != mCurrentSelect) {
NS_RELEASE(mCurrentSelect);
}
nsresult rv = NS_NewHTMLSelect(&mCurrentSelect, atom, mCurrentForm);
1998-04-22 23:24:43 +00:00
if ((NS_OK == rv) && (nsnull != mCurrentSelect)) {
// Add remaining attributes from the tag
1998-05-08 23:08:43 +00:00
//rv = AddAttributes(aNode, mCurrentSelect);
1998-04-22 23:24:43 +00:00
*aInstancePtrResult = mCurrentSelect;
}
NS_RELEASE(atom);
return rv;
}
nsresult
HTMLContentSink::ProcessCloseSELECTTag(const nsIParserNode& aNode)
{
NS_IF_RELEASE(mCurrentSelect);
mCurrentSelect = nsnull;
1998-04-22 23:24:43 +00:00
return NS_OK;
}
nsresult
1998-05-08 23:08:43 +00:00
HTMLContentSink::ProcessOpenOPTIONTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode)
1998-04-22 23:24:43 +00:00
{
nsresult rv = NS_OK;
if (nsnull != mCurrentSelect) {
if (nsnull != mCurrentOption) {
NS_RELEASE(mCurrentOption);
}
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
rv = NS_NewHTMLOption(&mCurrentOption, atom);
if ((NS_OK == rv) && (nsnull != mCurrentSelect)) {
// Add remaining attributes from the tag
1998-05-08 23:08:43 +00:00
//rv = AddAttributes(aNode, mCurrentOption);
*aInstancePtrResult = mCurrentSelect;
}
NS_RELEASE(atom);
1998-04-22 23:24:43 +00:00
}
return rv;
}
nsresult
HTMLContentSink::ProcessCloseOPTIONTag(const nsIParserNode& aNode)
{
NS_IF_RELEASE(mCurrentOption);
mCurrentOption = nsnull;
1998-04-22 23:24:43 +00:00
return NS_OK;
}
nsresult
HTMLContentSink::ProcessOPTIONTagContent(const nsIParserNode& aNode)
{
if ((nsnull != mCurrentSelect) && (nsnull != mCurrentOption)) {
nsIFormControl* control;
nsresult rv = mCurrentOption->QueryInterface(kIFormControlIID, (void **)&control);
1998-04-22 23:24:43 +00:00
nsAutoString currentText;
control->GetContent(currentText); // why do we need to do this
1998-04-22 23:24:43 +00:00
switch (aNode.GetTokenType()) {
case eToken_text:
case eToken_whitespace:
case eToken_newline:
currentText.Append(aNode.GetText());
break;
case eToken_entity:
{
nsAutoString tmp2("");
PRInt32 unicode = aNode.TranslateToUnicodeStr(tmp2);
if (unicode < 0) {
currentText.Append(aNode.GetText());
} else {
currentText.Append(tmp2);
}
}
break;
}
control->SetContent(currentText);
1998-04-22 23:24:43 +00:00
}
return NS_OK;
}
1998-04-13 20:24:54 +00:00
nsresult HTMLContentSink::ProcessWBRTag(nsIHTMLContent** aInstancePtrResult,
const nsIParserNode& aNode)
{
nsAutoString tmp(aNode.GetText());
tmp.ToUpperCase();
nsIAtom* atom = NS_NewAtom(tmp);
nsresult rv = NS_NewHTMLWordBreak(aInstancePtrResult, atom);
if (NS_OK == rv) {
rv = AddAttributes(aNode, *aInstancePtrResult);
}
NS_RELEASE(atom);
return rv;
}
1998-05-07 21:11:07 +00:00
/**
* This method gets called when the parser begins the process
* of building the content model via the content sink.
*
* @update 5/7/98 gess
*/
void HTMLContentSink::WillBuildModel(void){
}
/**
* This method gets called when the parser concludes the process
* of building the content model via the content sink.
*
* @update 5/7/98 gess
*/
void HTMLContentSink::DidBuildModel(void){
}
/**
* This method gets called when the parser gets i/o blocked,
* and wants to notify the sink that it may be a while before
* more data is available.
*
* @update 5/7/98 gess
*/
void HTMLContentSink::WillInterrupt(void) {
}
/**
* This method gets called when the parser i/o gets unblocked,
* and we're about to start dumping content again to the sink.
*
* @update 5/7/98 gess
*/
void HTMLContentSink::WillResume(void) {
}
1998-04-13 20:24:54 +00:00
//----------------------------------------------------------------------
nsresult NS_NewHTMLContentSink(nsIHTMLContentSink** aInstancePtrResult,
nsIDocument* aDoc,
nsIURL* aURL)
{
NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr");
if (nsnull == aInstancePtrResult) {
return NS_ERROR_NULL_POINTER;
}
HTMLContentSink* it = new HTMLContentSink();
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsresult rv = it->Init(aDoc, aURL);
if (NS_OK != rv) {
delete it;
return rv;
}
return it->QueryInterface(kIHTMLContentSinkIID, (void **)aInstancePtrResult);
}