/**********************************************************************
** Copyright (C) 2000-2008 Trolltech ASA.  All rights reserved.
**
** This file is part of the TQt Assistant.
**
** This file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free
** Software Foundation and appearing in the files LICENSE.GPL2
** and LICENSE.GPL3 included in the packaging of this file.
** Alternatively you may (at your option) use any later version
** of the GNU General Public License if such license has been
** publicly approved by Trolltech ASA (or its successors, if any)
** and the KDE Free TQt Foundation.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/.
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** Licensees holding valid TQt Commercial licenses may use this file in
** accordance with the TQt Commercial License Agreement provided with
** the Software.
**
** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
** herein.
**
**********************************************************************/

#include "docuparser.h"
#include "profile.h"

#include <ntqdir.h>
#include <ntqfile.h>
#include <ntqfileinfo.h>
#include <ntqregexp.h>
#include <ntqstring.h>
#include <ntqxml.h>

TQDataStream &operator>>( TQDataStream &s, ContentItem &ci )
{
    s >> ci.title;
    s >> ci.reference;
    s >> ci.depth;
    return s;
}

TQDataStream &operator<<( TQDataStream &s, const ContentItem &ci )
{
    s << ci.title;
    s << ci.reference;
    s << ci.depth;
    return s;
}

const TQString DocuParser::DocumentKey = "/TQt Assistant/" + TQString(TQT_VERSION_STR) + "/";

DocuParser *DocuParser::createParser( const TQString &fileName )
{
    TQFile file( fileName );
    if( !file.open( IO_ReadOnly ) ) {	
	return 0;	
    }
    
    TQString str;
    int read = 0;
    int maxlen = 1024;
    int majVer = 0, minVer = 0, serVer = 0;
    static TQRegExp re( "assistantconfig +version=\"(\\d)\\.(\\d)\\.(\\d)\"", FALSE );
    Q_ASSERT( re.isValid() );
    while( read != -1 ) {
 	read = file.readLine( str, maxlen );
	if( re.search( str ) >= 0 ) {
	    majVer = re.cap( 1 ).toInt();
	    minVer = re.cap( 2 ).toInt();
	    serVer = re.cap( 3 ).toInt();
	}
    }

    if( majVer == 3 && minVer >= 2 ) 
	return new DocuParser320;
    
    return new DocuParser310;	
}


bool DocuParser::parse( TQFile *file )
{
    TQXmlInputSource source( file );
    TQXmlSimpleReader reader;
    reader.setContentHandler( this );
    reader.setErrorHandler( this );
    setFileName( TQFileInfo( *file ).absFilePath() );
    return reader.parse( source );
}


TQString DocuParser::errorProtocol() const
{
    return errorProt;
}


TQValueList<ContentItem> DocuParser::getContentItems()
{
    return contentList;
}


TQPtrList<IndexItem> DocuParser::getIndexItems()
{
    return indexList;
}

TQString DocuParser::absolutify( const TQString &name ) const
{
    TQFileInfo orgPath( name );
    if( orgPath.isRelative() )
	return TQFileInfo( fname ).dirPath() + TQDir::separator() + name;
    return name;
}


void DocuParser310::addTo( Profile *p )
{
    p->addDCFTitle( fname, docTitle );
    p->addDCFIcon( docTitle, iconName );
    p->addDCFIndexPage( docTitle, conURL );
}


bool DocuParser310::startDocument()
{
    state = StateInit;
    errorProt = "";

    contentRef = "";
    indexRef = "";
    depth = 0;
    contentList.clear();
    indexList.clear();

    return TRUE;
}


bool DocuParser310::startElement( const TQString &, const TQString &,
			       const TQString &qname,
			       const TQXmlAttributes &attr )
{
    if (qname == "DCF" && state == StateInit) {
	state = StateContent;
	contentRef = absolutify( attr.value( "ref" ) );
	conURL = contentRef;
	docTitle = attr.value( "title" );
	iconName = absolutify( attr.value( "icon" ) );
	contentList.append( ContentItem( docTitle, contentRef, depth ) );
    } else if (qname == "section" && (state == StateContent || state == StateSect)) {
	state = StateSect;
	contentRef = absolutify( attr.value( "ref" ) );
	title = attr.value( "title" );
	depth++;
	contentList.append( ContentItem( title, contentRef, depth ) );
    } else if (qname == "keyword" && state == StateSect) {
	state = StateKeyword;
	indexRef = absolutify( attr.value( "ref" ) );
    } else
	return FALSE;
    return TRUE;
}

bool DocuParser310::endElement( const TQString &nameSpace, const TQString &localName,
			     const TQString &qName )
{
    switch( state ) {
    case StateInit:
	break;
    case StateContent:
	state = StateInit;
	break;
    case StateSect:
	state = --depth ? StateSect : StateContent;
	break;
    case StateKeyword:
	state = StateSect;
	break;
    }
    return TRUE;
}


bool DocuParser310::characters( const TQString& ch )
{
    TQString str = ch.simplifyWhiteSpace();
    if ( str.isEmpty() )
	return TRUE;

    switch ( state ) {
	case StateInit:
        case StateContent:
        case StateSect:
            return FALSE;
	    break;
        case StateKeyword:
	    indexList.append( new IndexItem( str, indexRef ) );
	    break;
	default:
            return FALSE;
    }
    return TRUE;
}


bool DocuParser310::fatalError( const TQXmlParseException& exception )
{
    errorProt += TQString( "fatal parsing error: %1 in line %2, column %3\n" )
        .arg( exception.message() )
        .arg( exception.lineNumber() )
        .arg( exception.columnNumber() );

    return TQXmlDefaultHandler::fatalError( exception );
}


DocuParser320::DocuParser320()
    : prof( new Profile )
{
}


void DocuParser320::addTo( Profile *p )
{
    TQMap<TQString,TQString>::ConstIterator it;

    for (it = prof->dcfTitles.begin(); it != prof->dcfTitles.end(); ++it)
	p->dcfTitles[it.key()] = *it;

    for (it = prof->icons.begin(); it != prof->icons.end(); ++it)
	p->icons[it.key()] = *it;

    for (it = prof->indexPages.begin(); it != prof->indexPages.end(); ++it)
	p->indexPages[it.key()] = *it;
}


bool DocuParser320::startDocument()
{
    state = StateInit;
    errorProt = "";

    contentRef = "";
    indexRef = "";
    depth = 0;
    contentList.clear();
    indexList.clear();

    prof->addDCF( fname );

    return TRUE;
}

bool DocuParser320::startElement( const TQString &, const TQString &,
			       const TQString &qname,
			       const TQXmlAttributes &attr )
{
    TQString lower = qname.lower();

    switch( state ) {
	
    case StateInit:
	if( lower == "assistantconfig" )	    
	    state = StateDocRoot;
	break;

    case StateDocRoot:
	if( lower == "dcf" ) {
	    state = StateContent;
	    contentRef = absolutify( attr.value( "ref" ) );
	    conURL = contentRef;
	    docTitle = attr.value( "title" );
	    iconName = absolutify( attr.value( "icon" ) );
	    contentList.append( ContentItem( docTitle, contentRef, depth ) );
	} else if( lower == "profile" ) {
	    state = StateProfile;
	}
	break;

    case StateSect:
	if ( lower == "keyword" && state == StateSect ) {
	    state = StateKeyword;
	    indexRef = absolutify( attr.value( "ref" ) );
	    break;
	} // else if (lower == "section")
    case StateContent:
	if( lower == "section" ) {
	    state = StateSect;
	    contentRef = absolutify( attr.value( "ref" ) );
	    title = attr.value( "title" );
	    depth++;
	    contentList.append( ContentItem( title, contentRef, depth ) );
	}
	break;

    case StateProfile:
	if( lower == "property" ) {
	    state = StateProperty;
	    propertyName = attr.value( "name" );
	}
	break;

    case StateProperty:
	break;
    }    
    
    return TRUE;
}

bool DocuParser320::endElement( const TQString &nameSpace,
				const TQString &localName,
				const TQString &qName )
{
    switch( state ) {
    case StateInit:
	break;
    case StateDocRoot:
	state = StateInit;
	break;
    case StateProfile:
	state = StateDocRoot;
	break;
    case StateProperty:
	state = StateProfile;
	if( propertyName.isEmpty() || propertyValue.isEmpty() )
	    return FALSE;
	{
	    static const TQStringList lst = TQStringList() << "startpage" << "abouturl"
							 << "applicationicon" << "assistantdocs";
	    if (lst.contains(propertyName))
		propertyValue = absolutify( propertyValue );
	}
	prof->addProperty( propertyName, propertyValue );
	break;
    case StateContent:
	if( !iconName.isEmpty() ) prof->addDCFIcon( docTitle, iconName );
	if( contentRef.isEmpty() )
	    return FALSE;
	prof->addDCFIndexPage( docTitle, conURL );
	prof->addDCFTitle( fname, docTitle );
	state = StateDocRoot;
	break;
    case StateSect:
	state = --depth ? StateSect : StateContent;
	break;
    case StateKeyword:
	state = StateSect;
	break;
    }
    return TRUE;
}

bool DocuParser320::characters( const TQString& ch )
{
    TQString str = ch.simplifyWhiteSpace();
    if ( str.isEmpty() )
	return TRUE;

    switch ( state ) {
    case StateInit:
    case StateContent:
    case StateSect:
	return FALSE;
	break;
    case StateKeyword:
	indexList.append( new IndexItem( str, indexRef ) );
	break;
    case StateProperty:
        propertyValue = ch;	
	break;
    default:
	return FALSE;
    }
    return TRUE;
}

bool DocuParser320::fatalError( const TQXmlParseException& exception )
{
    errorProt += TQString( "fatal parsing error: %1 in line %2, column %3\n" )
        .arg( exception.message() )
        .arg( exception.lineNumber() )
        .arg( exception.columnNumber() );

    return TQXmlDefaultHandler::fatalError( exception );
}