summaryrefslogtreecommitdiffstats
path: root/filters/kword/msword/texthandler.cpp
blob: d25408693d717604a951eae1d930dd89432f8880 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
/* This file is part of the KOffice project
   Copyright (C) 2002 Werner Trobin <trobin@kde.org>
   Copyright (C) 2002 David Faure <faure@kde.org>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License version 2 as published by the Free Software Foundation.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include "texthandler.h"
#include "conversion.h"

#include <wv2/styles.h>
#include <wv2/lists.h>
#include <wv2/paragraphproperties.h>
#include <wv2/functor.h>
#include <wv2/functordata.h>
#include <wv2/ustring.h>
#include <wv2/parser.h>
#include <wv2/fields.h>

#include <tqfont.h>
#include <tqfontinfo.h>
#include <kdebug.h>
#include <tdelocale.h>


wvWare::U8 KWordReplacementHandler::hardLineBreak()
{
    return '\n';
}

wvWare::U8 KWordReplacementHandler::nonBreakingHyphen()
{
    return '-'; // normal hyphen for now
}

wvWare::U8 KWordReplacementHandler::nonRequiredHyphen()
{
    return 0xad; // soft hyphen, according to kword.dtd
}


KWordTextHandler::KWordTextHandler( wvWare::SharedPtr<wvWare::Parser> parser )
    : m_parser( parser ), m_sectionNumber( 0 ), m_footNoteNumber( 0 ), m_endNoteNumber( 0 ),
      m_previousOutlineLSID( 0 ), m_previousEnumLSID( 0 ),
      m_currentStyle( 0L ), m_index( 0 ),
      m_currentTable( 0L ),
      m_bInParagraph( false ),
      m_insideField( false ), m_fieldAfterSeparator( false ), m_fieldType( 0 )
{
}

void KWordTextHandler::sectionStart( wvWare::SharedPtr<const wvWare::Word97::SEP> sep )
{
    m_sectionNumber++;

    if ( m_sectionNumber == 1 )
    {
        // KWord doesn't support a different paper format per section.
        // So we use the paper format of the first section,
        // and we apply it to the whole document.
        emit firstSectionFound( sep );
    }
    else
    {
        // Not the first section. Check for a page break
        if ( sep->bkc >= 1 ) // 1=new column, 2=new page, 3=even page, 4=odd page
        {
            pageBreak();
        }
    }
}

void KWordTextHandler::sectionEnd()
{

}

void KWordTextHandler::pageBreak()
{
    // Check if PAGEBREAKING already exists (e.g. due to linesTogether)
    TQDomElement pageBreak = m_oldLayout.namedItem( "PAGEBREAKING" ).toElement();
    if ( pageBreak.isNull() )
    {
        pageBreak = mainDocument().createElement( "PAGEBREAKING" );
        m_oldLayout.appendChild( pageBreak );
    }
    pageBreak.setAttribute( "hardFrameBreakAfter", "true" );
}

void KWordTextHandler::headersFound( const wvWare::HeaderFunctor& parseHeaders )
{
    // Currently we only care about headers in the first section
    if ( m_sectionNumber == 1 )
    {
        emit subDocFound( new wvWare::HeaderFunctor( parseHeaders ), 0 );
    }
}

void KWordTextHandler::footnoteFound( wvWare::FootnoteData::Type type,
                                      wvWare::UChar character, wvWare::SharedPtr<const wvWare::Word97::CHP> chp,
                                      const wvWare::FootnoteFunctor& parseFootnote )
{
    bool autoNumbered = (character.unicode() == 2);
    TQDomElement varElem = insertVariable( 11 /*KWord code for footnotes*/, chp, "STRI" );
    TQDomElement footnoteElem = varElem.ownerDocument().createElement( "FOOTNOTE" );
    if ( autoNumbered )
        footnoteElem.setAttribute( "value", 1 ); // KWord will renumber anyway
    else
        footnoteElem.setAttribute( "value", TQString(TQChar(character.unicode())) );
    footnoteElem.setAttribute( "notetype", type == wvWare::FootnoteData::Endnote ? "endnote" : "footnote" );
    footnoteElem.setAttribute( "numberingtype", autoNumbered ? "auto" : "manual" );
    if ( type == wvWare::FootnoteData::Endnote )
        // Keep name in sync with Document::startFootnote
        footnoteElem.setAttribute( "frameset", i18n("Endnote %1").arg( ++m_endNoteNumber ) );
    else
        // Keep name in sync with Document::startFootnote
        footnoteElem.setAttribute( "frameset", i18n("Footnote %1").arg( ++m_footNoteNumber ) );
    varElem.appendChild( footnoteElem );

    // Remember to parse the footnote text later
    emit subDocFound( new wvWare::FootnoteFunctor( parseFootnote ), type );
}

TQDomElement KWordTextHandler::insertVariable( int type, wvWare::SharedPtr<const wvWare::Word97::CHP> chp, const TQString& format )
{
    m_paragraph += '#';

    TQDomElement formatElem;
    writeFormat( m_formats, chp, m_currentStyle ? &m_currentStyle->chp() : 0, m_index, 1, 4 /*id*/, &formatElem );

    m_index += 1;

    TQDomElement varElem = m_formats.ownerDocument().createElement( "VARIABLE" );
    TQDomElement typeElem = m_formats.ownerDocument().createElement( "TYPE" );
    typeElem.setAttribute( "type", type );
    typeElem.setAttribute( "key", format );
    varElem.appendChild( typeElem );
    formatElem.appendChild( varElem );
    return varElem;
}

void KWordTextHandler::tableRowFound( const wvWare::TableRowFunctor& functor, wvWare::SharedPtr<const wvWare::Word97::TAP> tap )
{
    if ( !m_currentTable )
    {
        // We need to put the table in a paragraph. For wv2 tables are between paragraphs.
        Q_ASSERT( !m_bInParagraph );
        paragraphStart( 0L );
        static int s_tableNumber = 0;
        m_currentTable = new KWord::Table();
        m_currentTable->name = i18n("Table %1").arg( ++s_tableNumber );
        insertAnchor( m_currentTable->name );
    }

    // Add all cell edges to our array.
    for (int i = 0; i <= tap->itcMac; i++)
        m_currentTable->cacheCellEdge( tap->rgdxaCenter[ i ] );

    KWord::Row row( new wvWare::TableRowFunctor( functor ), tap );
    m_currentTable->rows.append( row );
}

#ifdef IMAGE_IMPORT
void KWordTextHandler::pictureFound( const wvWare::PictureFunctor& pictureFunctor,
                                     wvWare::SharedPtr<const wvWare::Word97::PICF> picf,
                                     wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
{
    static unsigned int s_pictureNumber = 0;
    TQString pictureName = "pictures/picture";
    pictureName += TQString::number( s_pictureNumber ); // filenames start at 0
    // looks better to the user if frame names start at 1
    TQString frameName = i18n("Picture %1").arg( ++s_pictureNumber );
    insertAnchor( frameName );

    switch ( picf->mfp.mm ) {
        case 98:
            pictureName += ".tif"; // not implemented!
            break;
        case 99:
            pictureName += ".bmp";
            break;
        default:
            pictureName += ".wmf";
            break;
    }

    emit pictureFound( frameName, pictureName, new wvWare::PictureFunctor( pictureFunctor ) );
}
#endif // IMAGE_IMPORT

TQDomElement KWordTextHandler::insertAnchor( const TQString& fsname )
{
    m_paragraph += '#';

    // Can't call writeFormat, we have no chp.
    TQDomElement format( mainDocument().createElement( "FORMAT" ) );
    format.setAttribute( "id", 6 );
    format.setAttribute( "pos", m_index );
    format.setAttribute( "len", 1 );
    m_formats.appendChild( format );
    TQDomElement formatElem = format;

    m_index += 1;

    TQDomElement anchorElem = m_formats.ownerDocument().createElement( "ANCHOR" );
    anchorElem.setAttribute( "type", "frameset" );
    anchorElem.setAttribute( "instance", fsname );
    formatElem.appendChild( anchorElem );
    return anchorElem;
}

void KWordTextHandler::paragLayoutBegin()
{
}

void KWordTextHandler::paragraphStart( wvWare::SharedPtr<const wvWare::ParagraphProperties> paragraphProperties )
{
    if ( m_bInParagraph )
        paragraphEnd();
    m_bInParagraph = true;
    //kdDebug(30513) << "paragraphStart. style index:" << paragraphProperties->pap().istd << endl;
    m_formats = mainDocument().createElement( "FORMATS" );
    m_paragraphProperties = paragraphProperties;
    const wvWare::StyleSheet& styles = m_parser->styleSheet();
    m_currentStyle = 0;
    if ( paragraphProperties ) // Always set when called by wv2. But not set when called by tableStart.
    {
        m_currentStyle = styles.styleByIndex( paragraphProperties->pap().istd );
        Q_ASSERT( m_currentStyle );
    }
    paragLayoutBegin();
}

void KWordTextHandler::paragraphEnd()
{
    Q_ASSERT( m_bInParagraph );
    if ( m_currentTable )
    {
        emit tableFound( *m_currentTable );
        delete m_currentTable;
        m_currentTable = 0L;
    }
    if ( m_currentStyle ) {
        TQConstString styleName = Conversion::string( m_currentStyle->name() );
        writeOutParagraph( styleName.string(), m_paragraph );
    } else
        writeOutParagraph( "Standard", m_paragraph );
    m_bInParagraph = false;
}

void KWordTextHandler::fieldStart( const wvWare::FLD* fld, wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
{
    m_fieldType = Conversion::fldToFieldType( fld );
    m_insideField = true;
    m_fieldAfterSeparator = false;
    m_fieldValue = "";
}

void KWordTextHandler::fieldSeparator( const wvWare::FLD* /*fld*/, wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
{
    m_fieldAfterSeparator = true;
}

void KWordTextHandler::fieldEnd( const wvWare::FLD* /*fld*/, wvWare::SharedPtr<const wvWare::Word97::CHP> chp )
{
    // only for handled field type
    if( m_fieldType >= 0 )
    {
        TQDomElement varElem = insertVariable( 8, chp, "STRING" );
        TQDomElement fieldElem = varElem.ownerDocument().createElement( "FIELD" );
        fieldElem.setAttribute( "subtype", m_fieldType );
        fieldElem.setAttribute( "value", m_fieldValue );
        varElem.appendChild( fieldElem );
    }

    // reset
    m_fieldValue = "";
    m_fieldType = -1;
    m_insideField = false;
    m_fieldAfterSeparator = false;
}

void KWordTextHandler::runOfText( const wvWare::UString& text, wvWare::SharedPtr<const wvWare::Word97::CHP> chp )
{
    TQConstString newText( Conversion::string( text ) );
    //kdDebug(30513) << "runOfText: " << newText.string() << endl;

    // text after fieldStart and before fieldSeparator is useless
    if( m_insideField && !m_fieldAfterSeparator ) return;

    // if we can handle the field, consume the text
    if( m_insideField && m_fieldAfterSeparator && ( m_fieldType >= 0 ) )
    {
        m_fieldValue.append( newText.string() );
        return;
    }

    m_paragraph += newText.string();

    writeFormat( m_formats, chp, m_currentStyle ? &m_currentStyle->chp() : 0, m_index, text.length(), 1, 0L );

    m_index += text.length();

}

void KWordTextHandler::writeFormat( TQDomElement& parentElement, const wvWare::Word97::CHP* chp, const wvWare::Word97::CHP* refChp, int pos, int len, int formatId, TQDomElement* pChildElement )
{
    TQDomElement format( mainDocument().createElement( "FORMAT" ) );
    format.setAttribute( "id", formatId );
    format.setAttribute( "pos", pos );
    format.setAttribute( "len", len );

    if ( !refChp || refChp->cv != chp->cv )
    {
        TQColor color = Conversion::color( chp->cv, -1 );
        TQDomElement colorElem( mainDocument().createElement( "COLOR" ) );
        colorElem.setAttribute( "red", color.red() );
        colorElem.setAttribute( "blue", color.blue() );
        colorElem.setAttribute( "green", color.green() );
        format.appendChild( colorElem );
    }

    // Font name
    // TBD: We only use the Ascii font code. We should work out how/when to use the FE and Other font codes.
    if ( !refChp || refChp->ftcAscii != chp->ftcAscii )
    {
        TQString fontName = getFont( chp->ftcAscii );

        if ( !fontName.isEmpty() )
        {
            TQDomElement fontElem( mainDocument().createElement( "FONT" ) );
            fontElem.setAttribute( "name", fontName );
            format.appendChild( fontElem );
        }
    }

    if ( !refChp || refChp->hps != chp->hps )
    {
        //kdDebug(30513) << "        font size: " << chp->hps/2 << endl;
        TQDomElement fontSize( mainDocument().createElement( "SIZE" ) );
        fontSize.setAttribute( "value", (int)(chp->hps / 2) ); // hps is in half points
        format.appendChild( fontSize );
    }

    if ( !refChp || refChp->fBold != chp->fBold ) {
        TQDomElement weight( mainDocument().createElement( "WEIGHT" ) );
        weight.setAttribute( "value", chp->fBold ? 75 : 50 );
        format.appendChild( weight );
    }
    if ( !refChp || refChp->fItalic != chp->fItalic ) {
        TQDomElement italic( mainDocument().createElement( "ITALIC" ) );
        italic.setAttribute( "value", chp->fItalic ? 1 : 0 );
        format.appendChild( italic );
    }
    if ( !refChp || refChp->kul != chp->kul ) {
        TQDomElement underline( mainDocument().createElement( "UNDERLINE" ) );
        TQString val = (chp->kul == 0) ? "0" : "1";
        switch ( chp->kul ) {
        case 3: // double
            underline.setAttribute( "styleline", "solid" );
            val = "double";
            break;
        case 6: // thick
            underline.setAttribute( "styleline", "solid" );
            val = "single-bold";
            break;
        case 7:
            underline.setAttribute( "styleline", "dash" );
            break;
        case 4: // dotted
        case 8: // dot (not used, says the docu)
            underline.setAttribute( "styleline", "dot" );
            break;
        case 9:
            underline.setAttribute( "styleline", "dashdot" );
            break;
        case 10:
            underline.setAttribute( "styleline", "dashdotdot" );
            break;
        case 11: // wave
            underline.setAttribute( "styleline", "wave" );
            break;
        case 5: // hidden - This makes no sense as an underline property!
            val = "0";
            break;
        case 1: // single
        case 2: // by word - TODO
        default:
            underline.setAttribute( "styleline", "solid" );
        };
        underline.setAttribute( "value", val );
        format.appendChild( underline );
    }
    if ( !refChp || refChp->fStrike != chp->fStrike || refChp->fDStrike != chp->fDStrike ) {
        TQDomElement strikeout( mainDocument().createElement( "STRIKEOUT" ) );
        if ( chp->fDStrike ) // double strikethrough
        {
            strikeout.setAttribute( "value", "double" );
            strikeout.setAttribute( "styleline", "solid" );
        }
        else if ( chp->fStrike )
        {
            strikeout.setAttribute( "value", "single" );
            strikeout.setAttribute( "styleline", "solid" );
        }
        else
            strikeout.setAttribute( "value", "0" );
        format.appendChild( strikeout );
    }

    // font attribute (uppercase, lowercase (not in MSWord), small caps)
    if ( !refChp || refChp->fCaps != chp->fCaps || refChp->fSmallCaps != chp->fSmallCaps )
    {
        TQDomElement fontAttrib( mainDocument().createElement( "FONTATTRIBUTE" ) );
        fontAttrib.setAttribute( "value", chp->fSmallCaps ? "smallcaps" : chp->fCaps ? "uppercase" : "none" );
        format.appendChild( fontAttrib );
    }
    if ( !refChp || refChp->iss != chp->iss ) { // superscript/subscript
        TQDomElement vertAlign( mainDocument().createElement( "VERTALIGN" ) );
        // Obviously the values are reversed between the two file formats :)
        int kwordVAlign = (chp->iss==1 ? 2 : chp->iss==2 ? 1 : 0);
        vertAlign.setAttribute( "value", kwordVAlign );
        format.appendChild( vertAlign );
    }

    // background color is known as "highlight" in msword
    if ( !refChp || refChp->fHighlight != chp->fHighlight || refChp->icoHighlight != chp->icoHighlight ) {
        TQDomElement bgcolElem( mainDocument().createElement( "TEXTBACKGROUNDCOLOR" ) );
        if ( chp->fHighlight )
        {
            TQColor color = Conversion::color( chp->icoHighlight, -1 );
            bgcolElem.setAttribute( "red", color.red() );
            bgcolElem.setAttribute( "blue", color.blue() );
            bgcolElem.setAttribute( "green", color.green() );
        } else {
            bgcolElem.setAttribute( "red", -1 );
            bgcolElem.setAttribute( "blue", -1 );
            bgcolElem.setAttribute( "green", -1 );
        }
        format.appendChild( bgcolElem );
    }

    // Shadow text. Only on/off. The properties are defined at the paragraph level (in KWord).
    if ( !refChp || refChp->fShadow != chp->fShadow || refChp->fImprint != chp->fImprint ) {
        TQDomElement shadowElem( mainDocument().createElement( "SHADOW" ) );
        TQString css = "none";
        // Generate a shadow with hardcoded values that make it look like in MSWord.
        // We need to make the distance depend on the font size though, to look good
        if (chp->fShadow || chp->fImprint)
        {
            int fontSize = (int)(chp->hps / 2);
            int dist = fontSize > 20 ? 2 : 1;
            if (chp->fImprint) // ## no real support for imprint, we use a topleft shadow
                dist = -dist;
            css = TQString::fromLatin1("#bebebe %1pt %1pt").arg(dist).arg(dist);
        }
        shadowElem.setAttribute( "text-shadow", css );
        format.appendChild( shadowElem );
    }

    if ( pChildElement || !format.firstChild().isNull() ) // Don't save an empty format tag, unless the caller asked for it
        parentElement.appendChild( format );
    if ( pChildElement )
        *pChildElement = format;
}

//#define FONT_DEBUG

// Return the name of a font. We have to convert the Microsoft font names to
// something that might just be present under X11.
TQString KWordTextHandler::getFont(unsigned fc) const
{
    Q_ASSERT( m_parser );
    if ( !m_parser )
        return TQString();
    const wvWare::Word97::FFN& ffn ( m_parser->font( fc ) );

    TQConstString fontName( Conversion::string( ffn.xszFfn ) );
    TQString font = fontName.string();

#ifdef FONT_DEBUG
    kdDebug(30513) << "    MS-FONT: " << font << endl;
#endif

    static const unsigned ENTRIES = 6;
    static const char* const fuzzyLookup[ENTRIES][2] =
    {
        // MS contains      X11 font family
        // substring.       non-Xft name.
        { "times",          "times" },
        { "courier",        "courier" },
        { "andale",         "monotype" },
        { "monotype.com",   "monotype" },
        { "georgia",        "times" },
        { "helvetica",      "helvetica" }
    };

    // When Xft is available, TQt will do a good job of looking up our local
    // equivalent of the MS font. But, we want to work even without Xft.
    // So, first, we do a fuzzy match of some common MS font names.
    unsigned i;

    for (i = 0; i < ENTRIES; i++)
    {
        // The loop will leave unchanged any MS font name not fuzzy-matched.
        if (font.find(fuzzyLookup[i][0], 0, FALSE) != -1)
        {
            font = fuzzyLookup[i][1];
            break;
        }
    }

#ifdef FONT_DEBUG
    kdDebug(30513) << "    FUZZY-FONT: " << font << endl;
#endif

    // Use TQt to look up our canonical equivalent of the font name.
    TQFont xFont( font );
    TQFontInfo info( xFont );

#ifdef FONT_DEBUG
    kdDebug(30513) << "    QT-FONT: " << info.family() << endl;
#endif

    return info.family();
}

void KWordTextHandler::writeOutParagraph( const TQString& styleName, const TQString& text )
{
    if ( m_framesetElement.isNull() )
    {
        if ( !text.isEmpty() ) // vertically merged table cells are ignored, and have empty text -> no warning on those
            kdWarning(30513) << "KWordTextHandler: no frameset element to write to! text=" << text << endl;
        return;
    }
    TQDomElement paragraphElementOut=mainDocument().createElement("PARAGRAPH");
    m_framesetElement.appendChild(paragraphElementOut);
    TQDomElement textElement=mainDocument().createElement("TEXT");
    textElement.setAttribute( "xml:space", "preserve" );
    paragraphElementOut.appendChild(textElement);
    paragraphElementOut.appendChild( m_formats );
    TQDomElement layoutElement=mainDocument().createElement("LAYOUT");
    paragraphElementOut.appendChild(layoutElement);

    TQDomElement nameElement = mainDocument().createElement("NAME");
    nameElement.setAttribute("value", styleName);
    layoutElement.appendChild(nameElement);

    if ( m_paragraphProperties )
    {
        // Write out the properties of the paragraph
        writeLayout( layoutElement, *m_paragraphProperties, m_currentStyle );
    }

    textElement.appendChild(mainDocument().createTextNode(text));

    m_paragraph = TQString( "" );
    m_index = 0;
    m_oldLayout = layoutElement; // Keep a reference to the old layout for some hacks
}

void KWordTextHandler::writeLayout( TQDomElement& parentElement, const wvWare::ParagraphProperties& paragraphProperties, const wvWare::Style* style )
{
    const wvWare::Word97::PAP& pap = paragraphProperties.pap();
    // Always write out the alignment, it's required
    TQDomElement flowElement = mainDocument().createElement("FLOW");
    TQString alignment = Conversion::alignment( pap.jc );
    flowElement.setAttribute( "align", alignment );
    parentElement.appendChild( flowElement );

    //kdDebug(30513) << k_funcinfo << " dxaLeft1=" << pap.dxaLeft1 << " dxaLeft=" << pap.dxaLeft << " dxaRight=" << pap.dxaRight << " dyaBefore=" << pap.dyaBefore << " dyaAfter=" << pap.dyaAfter << " lspd=" << pap.lspd.dyaLine << "/" << pap.lspd.fMultLinespace << endl;

    if ( pap.dxaLeft1 || pap.dxaLeft || pap.dxaRight )
    {
        TQDomElement indentsElement = mainDocument().createElement("INDENTS");
        // 'first' is relative to 'left' in both formats
        indentsElement.setAttribute( "first", (double)pap.dxaLeft1 / 20.0 );
        indentsElement.setAttribute( "left", (double)pap.dxaLeft / 20.0 );
        indentsElement.setAttribute( "right", (double)pap.dxaRight / 20.0 );
        parentElement.appendChild( indentsElement );
    }
    if ( pap.dyaBefore || pap.dyaAfter )
    {
        TQDomElement offsetsElement = mainDocument().createElement("OFFSETS");
        offsetsElement.setAttribute( "before", (double)pap.dyaBefore / 20.0 );
        offsetsElement.setAttribute( "after", (double)pap.dyaAfter / 20.0 );
        parentElement.appendChild( offsetsElement );
    }

    // Linespacing
    TQString lineSpacing = Conversion::lineSpacing( pap.lspd );
    if ( lineSpacing != "0" )
    {
        TQDomElement lineSpacingElem = mainDocument().createElement( "LINESPACING" );
        lineSpacingElem.setAttribute("value", lineSpacing );
        parentElement.appendChild( lineSpacingElem );
    }

    if ( pap.fKeep || pap.fKeepFollow || pap.fPageBreakBefore )
    {
        TQDomElement pageBreak = mainDocument().createElement( "PAGEBREAKING" );
        if ( pap.fKeep )
            pageBreak.setAttribute("linesTogether", "true");
        if ( pap.fPageBreakBefore )
            pageBreak.setAttribute("hardFrameBreak", "true" );
        if ( pap.fKeepFollow )
            pageBreak.setAttribute("keepWithNext", "true" );
        parentElement.appendChild( pageBreak );
    }

    // Borders
    if ( pap.brcTop.brcType )
    {
        TQDomElement borderElement = mainDocument().createElement( "TOPBORDER" );
        Conversion::setBorderAttributes( borderElement, pap.brcTop );
        parentElement.appendChild( borderElement );
    }
    if ( pap.brcBottom.brcType )
    {
        TQDomElement borderElement = mainDocument().createElement( "BOTTOMBORDER" );
        Conversion::setBorderAttributes( borderElement, pap.brcBottom );
        parentElement.appendChild( borderElement );
    }
    if ( pap.brcLeft.brcType )
    {
        TQDomElement borderElement = mainDocument().createElement( "LEFTBORDER" );
        Conversion::setBorderAttributes( borderElement, pap.brcLeft );
        parentElement.appendChild( borderElement );
    }
    if ( pap.brcRight.brcType )
    {
        TQDomElement borderElement = mainDocument().createElement( "RIGHTBORDER" );
        Conversion::setBorderAttributes( borderElement, pap.brcRight );
        parentElement.appendChild( borderElement );
    }

    // Tabulators
    if ( pap.itbdMac )
    {
        for ( int i = 0 ; i < pap.itbdMac ; ++i )
        {
            const wvWare::Word97::TabDescriptor &td = pap.rgdxaTab[i];
            TQDomElement tabElement = mainDocument().createElement( "TABULATOR" );
            tabElement.setAttribute( "ptpos", (double)td.dxaTab / 20.0 );
            //kdDebug(30513) << "ptpos=" << (double)td.dxaTab / 20.0 << endl;
            // Wow, lucky here. The type enum matches. Only, MSWord has 4=bar,
            // which kword doesn't support. We map it to 0 with a clever '%4' :)
            tabElement.setAttribute( "type", td.tbd.jc % 4 );
            int filling = 0;
            double width = 0.5; // default kword value, see koparaglayout.cpp
            switch ( td.tbd.tlc ) {
            case 1: // dots
            case 2: // hyphenated
                filling = 1; // KWord: dots
                break;
            case 3: // single line
                filling = 2; // KWord: line
                break;
            case 4: // heavy line
                filling = 2; // KWord: line
                width = 2; // make it heavy. To be tested.
            }
            tabElement.setAttribute( "filling", filling );
            tabElement.setAttribute( "width", width );
            parentElement.appendChild( tabElement );
        }
    }

    if ( pap.ilfo > 0 )
    {
        writeCounter( parentElement, paragraphProperties, style );
    }
}

void KWordTextHandler::writeCounter( TQDomElement& parentElement, const wvWare::ParagraphProperties& paragraphProperties, const wvWare::Style* style )
{
    const wvWare::ListInfo* listInfo = paragraphProperties.listInfo();
    if ( !listInfo )
        return;

#ifndef NDEBUG
    listInfo->dump();
#endif

    TQDomElement counterElement = mainDocument().createElement( "COUNTER" );
    // numbering type: 0==list 1==chapter. First we determine it for word6 docs.
    // But we can also activate it if the text() looks that way
    int numberingType = listInfo->isWord6() && listInfo->prev() ? 1 : 0;
    wvWare::UString text = listInfo->text().text;
    int nfc = listInfo->numberFormat();
    if ( nfc == 23 ) // bullet
    {
        if ( text.length() == 1 )
        {
            unsigned int code = text[0].unicode();
            if ( (code & 0xFF00) == 0xF000 ) // see wv2
                code &= 0x00FF;
            // Some well-known bullet codes. Better turn them into real
            // KWord bullets, it looks much nicer (than crappy fonts).
            if ( code == 0xB7 ) // Round black bullet
            {
                counterElement.setAttribute( "type", 10 ); // disc bullet
            } else if ( code == 0xD8 ) // Triangle
            {
                counterElement.setAttribute( "type", 11 ); // Box. We have no triangle.
            } else {
                // Map all other bullets to a "custom bullet" in kword.
                kdDebug(30513) << "custom bullet, code=" << TQString::number(code,16) << endl;
                counterElement.setAttribute( "type", 6 ); // custom
                counterElement.setAttribute( "bullet", code );
                TQString paragFont = getFont( style->chp().ftcAscii );
                counterElement.setAttribute( "bulletfont", paragFont );
            }
        } else
            kdWarning(30513) << "Bullet with more than one character, not supported" << endl;
    }
    else
    {
        const wvWare::Word97::PAP& pap = paragraphProperties.pap();
        counterElement.setAttribute( "start", listInfo->startAt() );

        int depth = pap.ilvl; /*both are 0 based*/
        // Heading styles don't set the ilvl, but must have a depth coming
        // from their heading level (the style's STI)
        bool isHeading = style->sti() >= 1 && style->sti() <= 9;
        if ( depth == 0 && isHeading )
        {
            depth = style->sti() - 1;
        }
        kdDebug(30513) << "  ilfo=" << pap.ilfo << " ilvl=" << pap.ilvl << " sti=" << style->sti() << " depth=" << depth << " numberingType=" << numberingType << endl;
        counterElement.setAttribute( "depth", depth );

        // Now we need to parse the text, to try and convert msword's powerful list template
        // stuff, into what KWord can do right now.
        TQString prefix, suffix;
        bool depthFound = false;
        int displayLevels = 1;
        // We parse <0>.<2>.<1>. as "level 2 with suffix='.'" (no prefix)
        // But "Section <0>)" has both prefix and suffix.
        // The common case is <0>.<1>.<2> (display-levels=3)
        for ( int i = 0 ; i < text.length() ; ++i )
        {
            short ch = text[i].unicode();
            //kdDebug(30513) << i << ":" << ch << endl;
            if ( ch < 10 ) { // List level place holder
                if ( ch == pap.ilvl ) {
                    if ( depthFound )
                        kdWarning(30513) << "ilvl " << pap.ilvl << " found twice in listInfo text..." << endl;
                    else
                        depthFound = true;
                    suffix = TQString();
                } else {
                    Q_ASSERT( ch < pap.ilvl ); // Can't see how level 1 would have a <0> in it...
                    if ( ch < pap.ilvl )
                        ++displayLevels; // we found a 'parent level', to be displayed
                    prefix = TQString(); // get rid of previous prefixes
                }
            } else { // Normal character
                if ( depthFound )
                    suffix += TQChar(ch);
                else
                    prefix += TQChar(ch);
            }
        }
        if ( displayLevels > 1 )
        {
            // This is a hierarchical list numbering e.g. <0>.<1>.
            // (unless this is about a heading, in which case we've set numberingtype to 1 already
            // so it will indeed look like that).
            // The question is whether the '.' is the suffix of the parent level already..
            if ( depth > 0 && !prefix.isEmpty() && m_listSuffixes[ depth - 1 ] == prefix )  {
                prefix = TQString(); // it's already the parent's suffix -> remove it
                kdDebug(30513) << "depth=" << depth << " parent suffix is " << prefix << " -> clearing" << endl;
            }
        }
        if ( isHeading )
            numberingType = 1;
        if ( depthFound )
        {
            // Word6 models "1." as nfc=5
            if ( nfc == 5 && suffix.isEmpty() )
                suffix = ".";
            kdDebug(30513) << " prefix=" << prefix << " suffix=" << suffix << endl;
            counterElement.setAttribute( "type", Conversion::numberFormatCode( nfc ) );
            counterElement.setAttribute( "lefttext", prefix );
            counterElement.setAttribute( "righttext", suffix );
            counterElement.setAttribute( "display-levels", displayLevels );
            kdDebug(30513) << "storing suffix " << suffix << " for depth " << depth << endl;
            m_listSuffixes[ depth ] = suffix;
        }
        else
        {
            kdWarning(30513) << "Not supported: counter text without the depth in it:" << Conversion::string(text).string() << endl;
        }

        if ( listInfo->startAtOverridden() ||
             ( numberingType == 1 && m_previousOutlineLSID != 0 && m_previousOutlineLSID != listInfo->lsid() ) ||
             ( numberingType == 0 &&m_previousEnumLSID != 0 && m_previousEnumLSID != listInfo->lsid() ) )
            counterElement.setAttribute( "restart", "true" );

        // listInfo->alignment() is not supported in KWord
        // listInfo->isLegal() hmm
        // listInfo->notRestarted() [by higher level of lists] not supported
        // listInfo->followingchar() ignored, it's always a space in KWord currently
    }
    if ( numberingType == 1 )
        m_previousOutlineLSID = listInfo->lsid();
    else
        m_previousEnumLSID = listInfo->lsid();
    counterElement.setAttribute( "numberingtype", numberingType );
    parentElement.appendChild( counterElement );
}

void KWordTextHandler::setFrameSetElement( const TQDomElement& frameset )
{
    m_framesetElement = frameset;
    for ( uint i = 0 ; i < 9 ; ++i )
        m_listSuffixes[i] = TQString();
}

TQDomDocument KWordTextHandler::mainDocument() const
{
    return m_framesetElement.ownerDocument();
}

#include "texthandler.moc"