/*
 * FCKeditor - The text editor for internet
 * Copyright (C) 2003-2004 Frederico Caldeira Knabben
 * 
 * Licensed under the terms of the GNU Lesser General Public License:
 * http://www.opensource.org/licenses/lgpl-license.php
 * 
 * For further information visit:
 * http://www.fckeditor.net/
 * 
 * File Name: fckxhtml.js
 * Defines the FCKXHtml object, responsible for the XHTML operations.
 * 
 * Version:  2.0 RC3
 * Modified: 2005-03-02 11:17:23
 * 
 * File Authors:
 * Frederico Caldeira Knabben (fredck@fckeditor.net)
 */

var FCKXHtml = new Object() ;

FCKXHtml.CurrentJobNum = 0 ;

FCKXHtml.GetXHTML = function( node, includeNode, format )
{
// Special blocks are blocks of content that remain untouched during the 
// process. It is used for SCRIPTs and STYLEs.
FCKXHtml.SpecialBlocks = new Array() ;

// Create the XML DOMDocument object.
this.XML = FCKTools.CreateXmlObject( 'DOMDocument' ) ;

// Add a root element that holds all child nodes.
this.MainNode = this.XML.appendChild( this.XML.createElement( 'xhtml' ) ) ;

FCKXHtml.CurrentJobNum++ ;

if ( includeNode )
this._AppendNode( this.MainNode, node ) ;
else
this._AppendChildNodes( this.MainNode, node, false ) ;

// Get the resulting XHTML as a string.
var sXHTML = this._GetMainXmlString() ;

// Strip the "XHTML" root node.
sXHTML = sXHTML.substr( 7, sXHTML.length - 15 ).trim() ;

if ( FCKConfig.ForceSimpleAmpersand )
sXHTML = sXHTML.replace( /___FCKAmp___/g, '&' ) ;

if ( format )
sXHTML = FCKCodeFormatter.Format( sXHTML ) ;

// Now we put back the SpecialBlocks contents.
for ( var i = 0 ; i < FCKXHtml.SpecialBlocks.length ; i++ )
{
var oRegex = new RegExp( '___FCKsi___' + i ) ;
sXHTML = sXHTML.replace( oRegex, FCKXHtml.SpecialBlocks[i] ) ;
}

this.XML = null ;

return sXHTML
}

FCKXHtml._AppendAttribute = function( xmlNode, attributeName, attributeValue )
{
try
{
// Create the attribute.
var oXmlAtt = this.XML.createAttribute( attributeName ) ;

oXmlAtt.value = attributeValue ? attributeValue : '' ;

// Set the attribute in the node.
xmlNode.attributes.setNamedItem( oXmlAtt ) ;
}
catch (e)
{}
}

FCKXHtml._AppendChildNodes = function( xmlNode, htmlNode, isBlockElement )
{
if ( htmlNode.hasChildNodes() )
{
// Get all children nodes.
var oChildren = htmlNode.childNodes ;

for ( var i = 0 ; i < oChildren.length ; i++ )
this._AppendNode( xmlNode, oChildren[i] ) ;
}
else
{
if ( isBlockElement && FCKConfig.FillEmptyBlocks )
{
this._AppendEntity( xmlNode, 'nbsp' ) ;
return ;
}

// We can't use short representation of empty elements that are not marked
// as empty in th XHTML DTD.
if ( ! FCKRegexLib.EmptyElements.test( htmlNode.nodeName ) )
xmlNode.appendChild( this.XML.createTextNode('') ) ;
}
}

FCKXHtml._AppendNode = function( xmlNode, htmlNode )
{
switch ( htmlNode.nodeType )
{
// Element Node.
case 1 :
// Mozilla insert custom nodes in the DOM.
if ( FCKBrowserInfo.IsGecko && htmlNode.hasAttribute('_moz_editor_bogus_node') )
return ;

// Create the Element.
var sNodeName = htmlNode.nodeName.toLowerCase() ;

if ( FCKBrowserInfo.IsGecko && sNodeName == 'br' && htmlNode.hasAttribute('type') && htmlNode.getAttribute( 'type', 2 ) == '_moz' )
return ;

// The already processed nodes must be marked to avoid then to be duplicated (bad formatted HTML).
// So here, the "mark" is checked... if the element is Ok, then mark it.
if ( htmlNode._fckxhtmljob == FCKXHtml.CurrentJobNum )
return ;
else
htmlNode._fckxhtmljob = FCKXHtml.CurrentJobNum ;

// If the nodeName starts with a slash, it is a orphan closing tag.
// On some strange cases, the nodeName is empty, even if the node exists.
if ( sNodeName.length == 0 || sNodeName.substr(0,1) == '/' )
break ;

var oNode = this.XML.createElement( sNodeName ) ;

// Add all attributes.
FCKXHtml._AppendAttributes( xmlNode, htmlNode, oNode, sNodeName ) ;

// Tag specific processing.
var oTagProcessor = FCKXHtml.TagProcessors[ sNodeName ] ;

if ( oTagProcessor )
{
oNode = oTagProcessor( oNode, htmlNode ) ;
if ( !oNode ) break ;
}
else
this._AppendChildNodes( oNode, htmlNode, FCKRegexLib.BlockElements.test( sNodeName ) ) ;

xmlNode.appendChild( oNode ) ;

break ;

// Text Node.
case 3 :
// We can't just replace the special chars with entities and create a
// text node with it. We must split the text isolating the special chars
// and add each piece a time.
var asPieces = htmlNode.nodeValue.replaceNewLineChars(' ').match( FCKXHtmlEntities.EntitiesRegex ) ;

if ( asPieces )
{
for ( var i = 0 ; i < asPieces.length ; i++ )
{
if ( asPieces[i].length == 1 )
{
var sEntity = FCKXHtmlEntities.Entities[ asPieces[i] ] ;
if ( sEntity != null )
{
this._AppendEntity( xmlNode, sEntity ) ;
continue ;
}
}
xmlNode.appendChild( this.XML.createTextNode( asPieces[i] ) ) ;
}
}

// This is the original code. It doesn't care about the entities.
//xmlNode.appendChild( this.XML.createTextNode( htmlNode.nodeValue ) ) ;

break ;

// Comment
case 8 :
xmlNode.appendChild( this.XML.createComment( htmlNode.nodeValue ) ) ;
break ;

// Unknown Node type.
default :
xmlNode.appendChild( this.XML.createComment( "Element not supported - Type: " + htmlNode.nodeType + " Name: " + htmlNode.nodeName ) ) ;
break ;
}
}

// Append an item to the SpecialBlocks array and returns the tag to be used.
FCKXHtml._AppendSpecialItem = function( item )
{
return '___FCKsi___' + FCKXHtml.SpecialBlocks.addItem( item ) ;
}

// An object that hold tag specific operations.
FCKXHtml.TagProcessors = new Object() ;

FCKXHtml.TagProcessors['img'] = function( node )
{
// The "ALT" attribute is required in XHTML.
if ( ! node.attributes.getNamedItem( 'alt' ) )
FCKXHtml._AppendAttribute( node, 'alt', '' ) ;

return node ;
}

FCKXHtml.TagProcessors['script'] = function( node, htmlNode )
{
// The "TYPE" attribute is required in XHTML.
if ( ! node.attributes.getNamedItem( 'type' ) )
FCKXHtml._AppendAttribute( node, 'type', 'text/javascript' ) ;

node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( htmlNode.text ) ) ) ;

return node ;
}

FCKXHtml.TagProcessors['style'] = function( node, htmlNode )
{
// The "_fcktemp" attribute is used to mark the <STYLE> used by the editor
// to set some behaviors.
if ( htmlNode.getAttribute( '_fcktemp' ) )
return null ;

// The "TYPE" attribute is required in XHTML.
if ( ! node.attributes.getNamedItem( 'type' ) )
FCKXHtml._AppendAttribute( node, 'type', 'text/css' ) ;

node.appendChild( FCKXHtml.XML.createTextNode( FCKXHtml._AppendSpecialItem( htmlNode.innerHTML ) ) ) ;

return node ;
}

FCKXHtml.TagProcessors['title'] = function( node, htmlNode )
{
node.appendChild( FCKXHtml.XML.createTextNode( FCK.EditorDocument.title ) ) ;

return node ;
}

FCKXHtml.TagProcessors['base'] = function( node, htmlNode )
{
// The "_fcktemp" attribute is used to mark the <BASE> tag when the editor
// automatically sets it using the FCKConfig.BaseHref configuration.
if ( htmlNode.getAttribute( '_fcktemp' ) )
return null ;

// IE duplicates the BODY inside the <BASE /> tag (don't ask me why!).
// This tag processor does nothing... in this way, no child nodes are added
// (also because the BASE tag must be empty).
return node ;
}

FCKXHtml.TagProcessors['link'] = function( node, htmlNode )
{
// The "_fcktemp" attribute is used to mark the fck_internal.css <LINK>
// reference.
if ( htmlNode.getAttribute( '_fcktemp' ) )
return null ;

return node ;
}

FCKXHtml.TagProcessors['table'] = function( node, htmlNode )
{
// There is a trick to show table borders when border=0. We add to the
// table class the FCK__ShowTableBorders rule. So now we must remove it.

var oClassAtt = node.attributes.getNamedItem( 'class' ) ;

if ( oClassAtt && FCKRegexLib.TableBorderClass.test( oClassAtt.nodeValue ) )
{
var sClass = oClassAtt.nodeValue.replace( FCKRegexLib.TableBorderClass, '' ) ;

if ( sClass.length == 0 )
node.attributes.removeNamedItem( 'class' ) ;
else
FCKXHtml._AppendAttribute( node, 'class', sClass ) ;
}

FCKXHtml._AppendChildNodes( node, htmlNode, false ) ;

return node ;
}
