summaryrefslogtreecommitdiffstats
path: root/Python/formatter_unicode.c
blob: e3a814984151b6e85c9afec1c2a090bee0b22513 (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
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
/* implements the unicode (as opposed to string) version of the
   built-in formatters for string, int, float.  that is, the versions
   of int.__float__, etc., that take and return unicode objects */

#include "Python.h"
#include <locale.h>

/* Raises an exception about an unknown presentation type for this
 * type. */

static void
unknown_presentation_type(Py_UCS4 presentation_type,
                          const char* type_name)
{
    /* %c might be out-of-range, hence the two cases. */
    if (presentation_type > 32 && presentation_type < 128)
        PyErr_Format(PyExc_ValueError,
                     "Unknown format code '%c' "
                     "for object of type '%.200s'",
                     (char)presentation_type,
                     type_name);
    else
        PyErr_Format(PyExc_ValueError,
                     "Unknown format code '\\x%x' "
                     "for object of type '%.200s'",
                     (unsigned int)presentation_type,
                     type_name);
}

static void
invalid_comma_type(Py_UCS4 presentation_type)
{
    if (presentation_type > 32 && presentation_type < 128)
        PyErr_Format(PyExc_ValueError,
                     "Cannot specify ',' with '%c'.",
                     (char)presentation_type);
    else
        PyErr_Format(PyExc_ValueError,
                     "Cannot specify ',' with '\\x%x'.",
                     (unsigned int)presentation_type);
}

/*
    get_integer consumes 0 or more decimal digit characters from an
    input string, updates *result with the corresponding positive
    integer, and returns the number of digits consumed.

    returns -1 on error.
*/
static int
get_integer(PyObject *str, Py_ssize_t *pos, Py_ssize_t end,
                  Py_ssize_t *result)
{
    Py_ssize_t accumulator, digitval;
    int numdigits;
    accumulator = numdigits = 0;
    for (;;(*pos)++, numdigits++) {
        if (*pos >= end)
            break;
        digitval = Py_UNICODE_TODECIMAL(PyUnicode_READ_CHAR(str, *pos));
        if (digitval < 0)
            break;
        /*
           Detect possible overflow before it happens:

              accumulator * 10 + digitval > PY_SSIZE_T_MAX if and only if
              accumulator > (PY_SSIZE_T_MAX - digitval) / 10.
        */
        if (accumulator > (PY_SSIZE_T_MAX - digitval) / 10) {
            PyErr_Format(PyExc_ValueError,
                         "Too many decimal digits in format string");
            return -1;
        }
        accumulator = accumulator * 10 + digitval;
    }
    *result = accumulator;
    return numdigits;
}

/************************************************************************/
/*********** standard format specifier parsing **************************/
/************************************************************************/

/* returns true if this character is a specifier alignment token */
Py_LOCAL_INLINE(int)
is_alignment_token(Py_UCS4 c)
{
    switch (c) {
    case '<': case '>': case '=': case '^':
        return 1;
    default:
        return 0;
    }
}

/* returns true if this character is a sign element */
Py_LOCAL_INLINE(int)
is_sign_element(Py_UCS4 c)
{
    switch (c) {
    case ' ': case '+': case '-':
        return 1;
    default:
        return 0;
    }
}


typedef struct {
    Py_UCS4 fill_char;
    Py_UCS4 align;
    int alternate;
    Py_UCS4 sign;
    Py_ssize_t width;
    int thousands_separators;
    Py_ssize_t precision;
    Py_UCS4 type;
} InternalFormatSpec;

#if 0
/* Occassionally useful for debugging. Should normally be commented out. */
static void
DEBUG_PRINT_FORMAT_SPEC(InternalFormatSpec *format)
{
    printf("internal format spec: fill_char %d\n", format->fill_char);
    printf("internal format spec: align %d\n", format->align);
    printf("internal format spec: alternate %d\n", format->alternate);
    printf("internal format spec: sign %d\n", format->sign);
    printf("internal format spec: width %zd\n", format->width);
    printf("internal format spec: thousands_separators %d\n",
           format->thousands_separators);
    printf("internal format spec: precision %zd\n", format->precision);
    printf("internal format spec: type %c\n", format->type);
    printf("\n");
}
#endif


/*
  ptr points to the start of the format_spec, end points just past its end.
  fills in format with the parsed information.
  returns 1 on success, 0 on failure.
  if failure, sets the exception
*/
static int
parse_internal_render_format_spec(PyObject *format_spec,
                                  Py_ssize_t start, Py_ssize_t end,
                                  InternalFormatSpec *format,
                                  char default_type,
                                  char default_align)
{
    Py_ssize_t pos = start;
    /* end-pos is used throughout this code to specify the length of
       the input string */
#define READ_spec(index) PyUnicode_READ_CHAR(format_spec, index)

    Py_ssize_t consumed;
    int align_specified = 0;
    int fill_char_specified = 0;

    format->fill_char = ' ';
    format->align = default_align;
    format->alternate = 0;
    format->sign = '\0';
    format->width = -1;
    format->thousands_separators = 0;
    format->precision = -1;
    format->type = default_type;

    /* If the second char is an alignment token,
       then parse the fill char */
    if (end-pos >= 2 && is_alignment_token(READ_spec(pos+1))) {
        format->align = READ_spec(pos+1);
        format->fill_char = READ_spec(pos);
        fill_char_specified = 1;
        align_specified = 1;
        pos += 2;
    }
    else if (end-pos >= 1 && is_alignment_token(READ_spec(pos))) {
        format->align = READ_spec(pos);
        align_specified = 1;
        ++pos;
    }

    /* Parse the various sign options */
    if (end-pos >= 1 && is_sign_element(READ_spec(pos))) {
        format->sign = READ_spec(pos);
        ++pos;
    }

    /* If the next character is #, we're in alternate mode.  This only
       applies to integers. */
    if (end-pos >= 1 && READ_spec(pos) == '#') {
        format->alternate = 1;
        ++pos;
    }

    /* The special case for 0-padding (backwards compat) */
    if (!fill_char_specified && end-pos >= 1 && READ_spec(pos) == '0') {
        format->fill_char = '0';
        if (!align_specified) {
            format->align = '=';
        }
        ++pos;
    }

    consumed = get_integer(format_spec, &pos, end, &format->width);
    if (consumed == -1)
        /* Overflow error. Exception already set. */
        return 0;

    /* If consumed is 0, we didn't consume any characters for the
       width. In that case, reset the width to -1, because
       get_integer() will have set it to zero. -1 is how we record
       that the width wasn't specified. */
    if (consumed == 0)
        format->width = -1;

    /* Comma signifies add thousands separators */
    if (end-pos && READ_spec(pos) == ',') {
        format->thousands_separators = 1;
        ++pos;
    }

    /* Parse field precision */
    if (end-pos && READ_spec(pos) == '.') {
        ++pos;

        consumed = get_integer(format_spec, &pos, end, &format->precision);
        if (consumed == -1)
            /* Overflow error. Exception already set. */
            return 0;

        /* Not having a precision after a dot is an error. */
        if (consumed == 0) {
            PyErr_Format(PyExc_ValueError,
                         "Format specifier missing precision");
            return 0;
        }

    }

    /* Finally, parse the type field. */

    if (end-pos > 1) {
        /* More than one char remain, invalid format specifier. */
        PyErr_Format(PyExc_ValueError, "Invalid format specifier");
        return 0;
    }

    if (end-pos == 1) {
        format->type = READ_spec(pos);
        ++pos;
    }

    /* Do as much validating as we can, just by looking at the format
       specifier.  Do not take into account what type of formatting
       we're doing (int, float, string). */

    if (format->thousands_separators) {
        switch (format->type) {
        case 'd':
        case 'e':
        case 'f':
        case 'g':
        case 'E':
        case 'G':
        case '%':
        case 'F':
        case '\0':
            /* These are allowed. See PEP 378.*/
            break;
        default:
            invalid_comma_type(format->type);
            return 0;
        }
    }

    assert (format->align <= 127);
    assert (format->sign <= 127);
    return 1;
}

/* Calculate the padding needed. */
static void
calc_padding(Py_ssize_t nchars, Py_ssize_t width, Py_UCS4 align,
             Py_ssize_t *n_lpadding, Py_ssize_t *n_rpadding,
             Py_ssize_t *n_total)
{
    if (width >= 0) {
        if (nchars > width)
            *n_total = nchars;
        else
            *n_total = width;
    }
    else {
        /* not specified, use all of the chars and no more */
        *n_total = nchars;
    }

    /* Figure out how much leading space we need, based on the
       aligning */
    if (align == '>')
        *n_lpadding = *n_total - nchars;
    else if (align == '^')
        *n_lpadding = (*n_total - nchars) / 2;
    else if (align == '<' || align == '=')
        *n_lpadding = 0;
    else {
        /* We should never have an unspecified alignment. */
        *n_lpadding = 0;
        assert(0);
    }

    *n_rpadding = *n_total - nchars - *n_lpadding;
}

/* Do the padding, and return a pointer to where the caller-supplied
   content goes. */
static int
fill_padding(_PyUnicodeWriter *writer,
             Py_ssize_t nchars,
             Py_UCS4 fill_char, Py_ssize_t n_lpadding,
             Py_ssize_t n_rpadding)
{
    Py_ssize_t pos;

    /* Pad on left. */
    if (n_lpadding) {
        pos = writer->pos;
        _PyUnicode_FastFill(writer->buffer, pos, n_lpadding, fill_char);
    }

    /* Pad on right. */
    if (n_rpadding) {
        pos = writer->pos + nchars + n_lpadding;
        _PyUnicode_FastFill(writer->buffer, pos, n_rpadding, fill_char);
    }

    /* Pointer to the user content. */
    writer->pos += n_lpadding;
    return 0;
}

/************************************************************************/
/*********** common routines for numeric formatting *********************/
/************************************************************************/

/* Locale type codes. */
#define LT_CURRENT_LOCALE 0
#define LT_DEFAULT_LOCALE 1
#define LT_NO_LOCALE 2

/* Locale info needed for formatting integers and the part of floats
   before and including the decimal. Note that locales only support
   8-bit chars, not unicode. */
typedef struct {
    PyObject *decimal_point;
    PyObject *thousands_sep;
    const char *grouping;
} LocaleInfo;

#define STATIC_LOCALE_INFO_INIT {0, 0, 0}

/* describes the layout for an integer, see the comment in
   calc_number_widths() for details */
typedef struct {
    Py_ssize_t n_lpadding;
    Py_ssize_t n_prefix;
    Py_ssize_t n_spadding;
    Py_ssize_t n_rpadding;
    char sign;
    Py_ssize_t n_sign;      /* number of digits needed for sign (0/1) */
    Py_ssize_t n_grouped_digits; /* Space taken up by the digits, including
                                    any grouping chars. */
    Py_ssize_t n_decimal;   /* 0 if only an integer */
    Py_ssize_t n_remainder; /* Digits in decimal and/or exponent part,
                               excluding the decimal itself, if
                               present. */

    /* These 2 are not the widths of fields, but are needed by
       STRINGLIB_GROUPING. */
    Py_ssize_t n_digits;    /* The number of digits before a decimal
                               or exponent. */
    Py_ssize_t n_min_width; /* The min_width we used when we computed
                               the n_grouped_digits width. */
} NumberFieldWidths;


/* Given a number of the form:
   digits[remainder]
   where ptr points to the start and end points to the end, find where
    the integer part ends. This could be a decimal, an exponent, both,
    or neither.
   If a decimal point is present, set *has_decimal and increment
    remainder beyond it.
   Results are undefined (but shouldn't crash) for improperly
    formatted strings.
*/
static void
parse_number(PyObject *s, Py_ssize_t pos, Py_ssize_t end,
             Py_ssize_t *n_remainder, int *has_decimal)
{
    Py_ssize_t remainder;

    while (pos<end && Py_ISDIGIT(PyUnicode_READ_CHAR(s, pos)))
        ++pos;
    remainder = pos;

    /* Does remainder start with a decimal point? */
    *has_decimal = pos<end && PyUnicode_READ_CHAR(s, remainder) == '.';

    /* Skip the decimal point. */
    if (*has_decimal)
        remainder++;

    *n_remainder = end - remainder;
}

/* not all fields of format are used.  for example, precision is
   unused.  should this take discrete params in order to be more clear
   about what it does?  or is passing a single format parameter easier
   and more efficient enough to justify a little obfuscation? */
static Py_ssize_t
calc_number_widths(NumberFieldWidths *spec, Py_ssize_t n_prefix,
                   Py_UCS4 sign_char, PyObject *number, Py_ssize_t n_start,
                   Py_ssize_t n_end, Py_ssize_t n_remainder,
                   int has_decimal, const LocaleInfo *locale,
                   const InternalFormatSpec *format, Py_UCS4 *maxchar)
{
    Py_ssize_t n_non_digit_non_padding;
    Py_ssize_t n_padding;

    spec->n_digits = n_end - n_start - n_remainder - (has_decimal?1:0);
    spec->n_lpadding = 0;
    spec->n_prefix = n_prefix;
    spec->n_decimal = has_decimal ? PyUnicode_GET_LENGTH(locale->decimal_point) : 0;
    spec->n_remainder = n_remainder;
    spec->n_spadding = 0;
    spec->n_rpadding = 0;
    spec->sign = '\0';
    spec->n_sign = 0;

    /* the output will look like:
       |                                                                                         |
       | <lpadding> <sign> <prefix> <spadding> <grouped_digits> <decimal> <remainder> <rpadding> |
       |                                                                                         |

       sign is computed from format->sign and the actual
       sign of the number

       prefix is given (it's for the '0x' prefix)

       digits is already known

       the total width is either given, or computed from the
       actual digits

       only one of lpadding, spadding, and rpadding can be non-zero,
       and it's calculated from the width and other fields
    */

    /* compute the various parts we're going to write */
    switch (format->sign) {
    case '+':
        /* always put a + or - */
        spec->n_sign = 1;
        spec->sign = (sign_char == '-' ? '-' : '+');
        break;
    case ' ':
        spec->n_sign = 1;
        spec->sign = (sign_char == '-' ? '-' : ' ');
        break;
    default:
        /* Not specified, or the default (-) */
        if (sign_char == '-') {
            spec->n_sign = 1;
            spec->sign = '-';
        }
    }

    /* The number of chars used for non-digits and non-padding. */
    n_non_digit_non_padding = spec->n_sign + spec->n_prefix + spec->n_decimal +
        spec->n_remainder;

    /* min_width can go negative, that's okay. format->width == -1 means
       we don't care. */
    if (format->fill_char == '0' && format->align == '=')
        spec->n_min_width = format->width - n_non_digit_non_padding;
    else
        spec->n_min_width = 0;

    if (spec->n_digits == 0)
        /* This case only occurs when using 'c' formatting, we need
           to special case it because the grouping code always wants
           to have at least one character. */
        spec->n_grouped_digits = 0;
    else {
        Py_UCS4 grouping_maxchar;
        spec->n_grouped_digits = _PyUnicode_InsertThousandsGrouping(
            NULL, 0,
            0, NULL,
            spec->n_digits, spec->n_min_width,
            locale->grouping, locale->thousands_sep, &grouping_maxchar);
        *maxchar = Py_MAX(*maxchar, grouping_maxchar);
    }

    /* Given the desired width and the total of digit and non-digit
       space we consume, see if we need any padding. format->width can
       be negative (meaning no padding), but this code still works in
       that case. */
    n_padding = format->width -
                        (n_non_digit_non_padding + spec->n_grouped_digits);
    if (n_padding > 0) {
        /* Some padding is needed. Determine if it's left, space, or right. */
        switch (format->align) {
        case '<':
            spec->n_rpadding = n_padding;
            break;
        case '^':
            spec->n_lpadding = n_padding / 2;
            spec->n_rpadding = n_padding - spec->n_lpadding;
            break;
        case '=':
            spec->n_spadding = n_padding;
            break;
        case '>':
            spec->n_lpadding = n_padding;
            break;
        default:
            /* Shouldn't get here, but treat it as '>' */
            spec->n_lpadding = n_padding;
            assert(0);
            break;
        }
    }

    if (spec->n_lpadding || spec->n_spadding || spec->n_rpadding)
        *maxchar = Py_MAX(*maxchar, format->fill_char);

    if (spec->n_decimal)
        *maxchar = Py_MAX(*maxchar, PyUnicode_MAX_CHAR_VALUE(locale->decimal_point));

    return spec->n_lpadding + spec->n_sign + spec->n_prefix +
        spec->n_spadding + spec->n_grouped_digits + spec->n_decimal +
        spec->n_remainder + spec->n_rpadding;
}

/* Fill in the digit parts of a numbers's string representation,
   as determined in calc_number_widths().
   Return -1 on error, or 0 on success. */
static int
fill_number(_PyUnicodeWriter *writer, const NumberFieldWidths *spec,
            PyObject *digits, Py_ssize_t d_start, Py_ssize_t d_end,
            PyObject *prefix, Py_ssize_t p_start,
            Py_UCS4 fill_char,
            LocaleInfo *locale, int toupper)
{
    /* Used to keep track of digits, decimal, and remainder. */
    Py_ssize_t d_pos = d_start;
    const unsigned int kind = writer->kind;
    const void *data = writer->data;
    Py_ssize_t r;

    if (spec->n_lpadding) {
        _PyUnicode_FastFill(writer->buffer,
                            writer->pos, spec->n_lpadding, fill_char);
        writer->pos += spec->n_lpadding;
    }
    if (spec->n_sign == 1) {
        PyUnicode_WRITE(kind, data, writer->pos, spec->sign);
        writer->pos++;
    }
    if (spec->n_prefix) {
        _PyUnicode_FastCopyCharacters(writer->buffer, writer->pos,
                                      prefix, p_start,
                                      spec->n_prefix);
        if (toupper) {
            Py_ssize_t t;
            for (t = 0; t < spec->n_prefix; t++) {
                Py_UCS4 c = PyUnicode_READ(kind, data, writer->pos + t);
                c = Py_TOUPPER(c);
                assert (c <= 127);
                PyUnicode_WRITE(kind, data, writer->pos + t, c);
            }
        }
        writer->pos += spec->n_prefix;
    }
    if (spec->n_spadding) {
        _PyUnicode_FastFill(writer->buffer,
                            writer->pos, spec->n_spadding, fill_char);
        writer->pos += spec->n_spadding;
    }

    /* Only for type 'c' special case, it has no digits. */
    if (spec->n_digits != 0) {
        /* Fill the digits with InsertThousandsGrouping. */
        char *pdigits;
        if (PyUnicode_READY(digits))
            return -1;
        pdigits = PyUnicode_DATA(digits);
        if (PyUnicode_KIND(digits) < kind) {
            pdigits = _PyUnicode_AsKind(digits, kind);
            if (pdigits == NULL)
                return -1;
        }
        r = _PyUnicode_InsertThousandsGrouping(
                writer->buffer, writer->pos,
                spec->n_grouped_digits,
                pdigits + kind * d_pos,
                spec->n_digits, spec->n_min_width,
                locale->grouping, locale->thousands_sep, NULL);
        if (r == -1)
            return -1;
        assert(r == spec->n_grouped_digits);
        if (PyUnicode_KIND(digits) < kind)
            PyMem_Free(pdigits);
        d_pos += spec->n_digits;
    }
    if (toupper) {
        Py_ssize_t t;
        for (t = 0; t < spec->n_grouped_digits; t++) {
            Py_UCS4 c = PyUnicode_READ(kind, data, writer->pos + t);
            c = Py_TOUPPER(c);
            if (c > 127) {
                PyErr_SetString(PyExc_SystemError, "non-ascii grouped digit");
                return -1;
            }
            PyUnicode_WRITE(kind, data, writer->pos + t, c);
        }
    }
    writer->pos += spec->n_grouped_digits;

    if (spec->n_decimal) {
        _PyUnicode_FastCopyCharacters(
            writer->buffer, writer->pos,
            locale->decimal_point, 0, spec->n_decimal);
        writer->pos += spec->n_decimal;
        d_pos += 1;
    }

    if (spec->n_remainder) {
        _PyUnicode_FastCopyCharacters(
            writer->buffer, writer->pos,
            digits, d_pos, spec->n_remainder);
        writer->pos += spec->n_remainder;
        /* d_pos += spec->n_remainder; */
    }

    if (spec->n_rpadding) {
        _PyUnicode_FastFill(writer->buffer,
                            writer->pos, spec->n_rpadding,
                            fill_char);
        writer->pos += spec->n_rpadding;
    }
    return 0;
}

static char no_grouping[1] = {CHAR_MAX};

/* Find the decimal point character(s?), thousands_separator(s?), and
   grouping description, either for the current locale if type is
   LT_CURRENT_LOCALE, a hard-coded locale if LT_DEFAULT_LOCALE, or
   none if LT_NO_LOCALE. */
static int
get_locale_info(int type, LocaleInfo *locale_info)
{
    switch (type) {
    case LT_CURRENT_LOCALE: {
        struct lconv *locale_data = localeconv();
        locale_info->decimal_point = PyUnicode_DecodeLocale(
                                         locale_data->decimal_point,
                                         NULL);
        if (locale_info->decimal_point == NULL)
            return -1;
        locale_info->thousands_sep = PyUnicode_DecodeLocale(
                                         locale_data->thousands_sep,
                                         NULL);
        if (locale_info->thousands_sep == NULL) {
            Py_DECREF(locale_info->decimal_point);
            return -1;
        }
        locale_info->grouping = locale_data->grouping;
        break;
    }
    case LT_DEFAULT_LOCALE:
        locale_info->decimal_point = PyUnicode_FromOrdinal('.');
        locale_info->thousands_sep = PyUnicode_FromOrdinal(',');
        if (!locale_info->decimal_point || !locale_info->thousands_sep) {
            Py_XDECREF(locale_info->decimal_point);
            Py_XDECREF(locale_info->thousands_sep);
            return -1;
        }
        locale_info->grouping = "\3"; /* Group every 3 characters.  The
                                         (implicit) trailing 0 means repeat
                                         infinitely. */
        break;
    case LT_NO_LOCALE:
        locale_info->decimal_point = PyUnicode_FromOrdinal('.');
        locale_info->thousands_sep = PyUnicode_New(0, 0);
        if (!locale_info->decimal_point || !locale_info->thousands_sep) {
            Py_XDECREF(locale_info->decimal_point);
            Py_XDECREF(locale_info->thousands_sep);
            return -1;
        }
        locale_info->grouping = no_grouping;
        break;
    default:
        assert(0);
    }
    return 0;
}

static void
free_locale_info(LocaleInfo *locale_info)
{
    Py_XDECREF(locale_info->decimal_point);
    Py_XDECREF(locale_info->thousands_sep);
}

/************************************************************************/
/*********** string formatting ******************************************/
/************************************************************************/

static int
format_string_internal(PyObject *value, const InternalFormatSpec *format,
                       _PyUnicodeWriter *writer)
{
    Py_ssize_t lpad;
    Py_ssize_t rpad;
    Py_ssize_t total;
    Py_ssize_t len;
    int result = -1;
    Py_UCS4 maxchar;

    assert(PyUnicode_IS_READY(value));
    len = PyUnicode_GET_LENGTH(value);

    /* sign is not allowed on strings */
    if (format->sign != '\0') {
        PyErr_SetString(PyExc_ValueError,
                        "Sign not allowed in string format specifier");
        goto done;
    }

    /* alternate is not allowed on strings */
    if (format->alternate) {
        PyErr_SetString(PyExc_ValueError,
                        "Alternate form (#) not allowed in string format "
                        "specifier");
        goto done;
    }

    /* '=' alignment not allowed on strings */
    if (format->align == '=') {
        PyErr_SetString(PyExc_ValueError,
                        "'=' alignment not allowed "
                        "in string format specifier");
        goto done;
    }

    if ((format->width == -1 || format->width <= len)
        && (format->precision == -1 || format->precision >= len)) {
        /* Fast path */
        return _PyUnicodeWriter_WriteStr(writer, value);
    }

    /* if precision is specified, output no more that format.precision
       characters */
    if (format->precision >= 0 && len >= format->precision) {
        len = format->precision;
    }

    calc_padding(len, format->width, format->align, &lpad, &rpad, &total);

    maxchar = writer->maxchar;
    if (lpad != 0 || rpad != 0)
        maxchar = Py_MAX(maxchar, format->fill_char);
    if (PyUnicode_MAX_CHAR_VALUE(value) > maxchar) {
        Py_UCS4 valmaxchar = _PyUnicode_FindMaxChar(value, 0, len);
        maxchar = Py_MAX(maxchar, valmaxchar);
    }

    /* allocate the resulting string */
    if (_PyUnicodeWriter_Prepare(writer, total, maxchar) == -1)
        goto done;

    /* Write into that space. First the padding. */
    result = fill_padding(writer, len, format->fill_char, lpad, rpad);
    if (result == -1)
        goto done;

    /* Then the source string. */
    if (len) {
        _PyUnicode_FastCopyCharacters(writer->buffer, writer->pos,
                                      value, 0, len);
    }
    writer->pos += (len + rpad);
    result = 0;

done:
    return result;
}


/************************************************************************/
/*********** long formatting ********************************************/
/************************************************************************/

static int
format_long_internal(PyObject *value, const InternalFormatSpec *format,
                     _PyUnicodeWriter *writer)
{
    int result = -1;
    Py_UCS4 maxchar = 127;
    PyObject *tmp = NULL;
    Py_ssize_t inumeric_chars;
    Py_UCS4 sign_char = '\0';
    Py_ssize_t n_digits;       /* count of digits need from the computed
                                  string */
    Py_ssize_t n_remainder = 0; /* Used only for 'c' formatting, which
                                   produces non-digits */
    Py_ssize_t n_prefix = 0;   /* Count of prefix chars, (e.g., '0x') */
    Py_ssize_t n_total;
    Py_ssize_t prefix = 0;
    NumberFieldWidths spec;
    long x;

    /* Locale settings, either from the actual locale or
       from a hard-code pseudo-locale */
    LocaleInfo locale = STATIC_LOCALE_INFO_INIT;

    /* no precision allowed on integers */
    if (format->precision != -1) {
        PyErr_SetString(PyExc_ValueError,
                        "Precision not allowed in integer format specifier");
        goto done;
    }

    /* special case for character formatting */
    if (format->type == 'c') {
        /* error to specify a sign */
        if (format->sign != '\0') {
            PyErr_SetString(PyExc_ValueError,
                            "Sign not allowed with integer"
                            " format specifier 'c'");
            goto done;
        }

        /* taken from unicodeobject.c formatchar() */
        /* Integer input truncated to a character */
        x = PyLong_AsLong(value);
        if (x == -1 && PyErr_Occurred())
            goto done;
        if (x < 0 || x > 0x10ffff) {
            PyErr_SetString(PyExc_OverflowError,
                            "%c arg not in range(0x110000)");
            goto done;
        }
        tmp = PyUnicode_FromOrdinal(x);
        inumeric_chars = 0;
        n_digits = 1;
        maxchar = Py_MAX(maxchar, (Py_UCS4)x);

        /* As a sort-of hack, we tell calc_number_widths that we only
           have "remainder" characters. calc_number_widths thinks
           these are characters that don't get formatted, only copied
           into the output string. We do this for 'c' formatting,
           because the characters are likely to be non-digits. */
        n_remainder = 1;
    }
    else {
        int base;
        int leading_chars_to_skip = 0;  /* Number of characters added by
                                           PyNumber_ToBase that we want to
                                           skip over. */

        /* Compute the base and how many characters will be added by
           PyNumber_ToBase */
        switch (format->type) {
        case 'b':
            base = 2;
            leading_chars_to_skip = 2; /* 0b */
            break;
        case 'o':
            base = 8;
            leading_chars_to_skip = 2; /* 0o */
            break;
        case 'x':
        case 'X':
            base = 16;
            leading_chars_to_skip = 2; /* 0x */
            break;
        default:  /* shouldn't be needed, but stops a compiler warning */
        case 'd':
        case 'n':
            base = 10;
            break;
        }

        if (format->sign != '+' && format->sign != ' '
            && format->width == -1
            && format->type != 'X' && format->type != 'n'
            && !format->thousands_separators
            && PyLong_CheckExact(value))
        {
            /* Fast path */
            return _PyLong_FormatWriter(writer, value, base, format->alternate);
        }

        /* The number of prefix chars is the same as the leading
           chars to skip */
        if (format->alternate)
            n_prefix = leading_chars_to_skip;

        /* Do the hard part, converting to a string in a given base */
        tmp = _PyLong_Format(value, base);
        if (tmp == NULL || PyUnicode_READY(tmp) == -1)
            goto done;

        inumeric_chars = 0;
        n_digits = PyUnicode_GET_LENGTH(tmp);

        prefix = inumeric_chars;

        /* Is a sign character present in the output?  If so, remember it
           and skip it */
        if (PyUnicode_READ_CHAR(tmp, inumeric_chars) == '-') {
            sign_char = '-';
            ++prefix;
            ++leading_chars_to_skip;
        }

        /* Skip over the leading chars (0x, 0b, etc.) */
        n_digits -= leading_chars_to_skip;
        inumeric_chars += leading_chars_to_skip;
    }

    /* Determine the grouping, separator, and decimal point, if any. */
    if (get_locale_info(format->type == 'n' ? LT_CURRENT_LOCALE :
                        (format->thousands_separators ?
                         LT_DEFAULT_LOCALE :
                         LT_NO_LOCALE),
                        &locale) == -1)
        goto done;

    /* Calculate how much memory we'll need. */
    n_total = calc_number_widths(&spec, n_prefix, sign_char, tmp, inumeric_chars,
                                 inumeric_chars + n_digits, n_remainder, 0,
                                 &locale, format, &maxchar);

    /* Allocate the memory. */
    if (_PyUnicodeWriter_Prepare(writer, n_total, maxchar) == -1)
        goto done;

    /* Populate the memory. */
    result = fill_number(writer, &spec,
                         tmp, inumeric_chars, inumeric_chars + n_digits,
                         tmp, prefix, format->fill_char,
                         &locale, format->type == 'X');

done:
    Py_XDECREF(tmp);
    free_locale_info(&locale);
    return result;
}

/************************************************************************/
/*********** float formatting *******************************************/
/************************************************************************/

/* much of this is taken from unicodeobject.c */
static int
format_float_internal(PyObject *value,
                      const InternalFormatSpec *format,
                      _PyUnicodeWriter *writer)
{
    char *buf = NULL;       /* buffer returned from PyOS_double_to_string */
    Py_ssize_t n_digits;
    Py_ssize_t n_remainder;
    Py_ssize_t n_total;
    int has_decimal;
    double val;
    int precision, default_precision = 6;
    Py_UCS4 type = format->type;
    int add_pct = 0;
    Py_ssize_t index;
    NumberFieldWidths spec;
    int flags = 0;
    int result = -1;
    Py_UCS4 maxchar = 127;
    Py_UCS4 sign_char = '\0';
    int float_type; /* Used to see if we have a nan, inf, or regular float. */
    PyObject *unicode_tmp = NULL;

    /* Locale settings, either from the actual locale or
       from a hard-code pseudo-locale */
    LocaleInfo locale = STATIC_LOCALE_INFO_INIT;

    if (format->precision > INT_MAX) {
        PyErr_SetString(PyExc_ValueError, "precision too big");
        goto done;
    }
    precision = (int)format->precision;

    if (format->alternate)
        flags |= Py_DTSF_ALT;

    if (type == '\0') {
        /* Omitted type specifier.  Behaves in the same way as repr(x)
           and str(x) if no precision is given, else like 'g', but with
           at least one digit after the decimal point. */
        flags |= Py_DTSF_ADD_DOT_0;
        type = 'r';
        default_precision = 0;
    }

    if (type == 'n')
        /* 'n' is the same as 'g', except for the locale used to
           format the result. We take care of that later. */
        type = 'g';

    val = PyFloat_AsDouble(value);
    if (val == -1.0 && PyErr_Occurred())
        goto done;

    if (type == '%') {
        type = 'f';
        val *= 100;
        add_pct = 1;
    }

    if (precision < 0)
        precision = default_precision;
    else if (type == 'r')
        type = 'g';

    /* Cast "type", because if we're in unicode we need to pass a
       8-bit char. This is safe, because we've restricted what "type"
       can be. */
    buf = PyOS_double_to_string(val, (char)type, precision, flags,
                                &float_type);
    if (buf == NULL)
        goto done;
    n_digits = strlen(buf);

    if (add_pct) {
        /* We know that buf has a trailing zero (since we just called
           strlen() on it), and we don't use that fact any more. So we
           can just write over the trailing zero. */
        buf[n_digits] = '%';
        n_digits += 1;
    }

    if (format->sign != '+' && format->sign != ' '
        && format->width == -1
        && format->type != 'n'
        && !format->thousands_separators)
    {
        /* Fast path */
        result = _PyUnicodeWriter_WriteASCIIString(writer, buf, n_digits);
        PyMem_Free(buf);
        return result;
    }

    /* Since there is no unicode version of PyOS_double_to_string,
       just use the 8 bit version and then convert to unicode. */
    unicode_tmp = _PyUnicode_FromASCII(buf, n_digits);
    PyMem_Free(buf);
    if (unicode_tmp == NULL)
        goto done;

    /* Is a sign character present in the output?  If so, remember it
       and skip it */
    index = 0;
    if (PyUnicode_READ_CHAR(unicode_tmp, index) == '-') {
        sign_char = '-';
        ++index;
        --n_digits;
    }

    /* Determine if we have any "remainder" (after the digits, might include
       decimal or exponent or both (or neither)) */
    parse_number(unicode_tmp, index, index + n_digits, &n_remainder, &has_decimal);

    /* Determine the grouping, separator, and decimal point, if any. */
    if (get_locale_info(format->type == 'n' ? LT_CURRENT_LOCALE :
                        (format->thousands_separators ?
                         LT_DEFAULT_LOCALE :
                         LT_NO_LOCALE),
                        &locale) == -1)
        goto done;

    /* Calculate how much memory we'll need. */
    n_total = calc_number_widths(&spec, 0, sign_char, unicode_tmp, index,
                                 index + n_digits, n_remainder, has_decimal,
                                 &locale, format, &maxchar);

    /* Allocate the memory. */
    if (_PyUnicodeWriter_Prepare(writer, n_total, maxchar) == -1)
        goto done;

    /* Populate the memory. */
    result = fill_number(writer, &spec,
                         unicode_tmp, index, index + n_digits,
                         NULL, 0, format->fill_char,
                         &locale, 0);

done:
    Py_XDECREF(unicode_tmp);
    free_locale_info(&locale);
    return result;
}

/************************************************************************/
/*********** complex formatting *****************************************/
/************************************************************************/

static int
format_complex_internal(PyObject *value,
                        const InternalFormatSpec *format,
                        _PyUnicodeWriter *writer)
{
    double re;
    double im;
    char *re_buf = NULL;       /* buffer returned from PyOS_double_to_string */
    char *im_buf = NULL;       /* buffer returned from PyOS_double_to_string */

    InternalFormatSpec tmp_format = *format;
    Py_ssize_t n_re_digits;
    Py_ssize_t n_im_digits;
    Py_ssize_t n_re_remainder;
    Py_ssize_t n_im_remainder;
    Py_ssize_t n_re_total;
    Py_ssize_t n_im_total;
    int re_has_decimal;
    int im_has_decimal;
    int precision, default_precision = 6;
    Py_UCS4 type = format->type;
    Py_ssize_t i_re;
    Py_ssize_t i_im;
    NumberFieldWidths re_spec;
    NumberFieldWidths im_spec;
    int flags = 0;
    int result = -1;
    Py_UCS4 maxchar = 127;
    enum PyUnicode_Kind rkind;
    void *rdata;
    Py_UCS4 re_sign_char = '\0';
    Py_UCS4 im_sign_char = '\0';
    int re_float_type; /* Used to see if we have a nan, inf, or regular float. */
    int im_float_type;
    int add_parens = 0;
    int skip_re = 0;
    Py_ssize_t lpad;
    Py_ssize_t rpad;
    Py_ssize_t total;
    PyObject *re_unicode_tmp = NULL;
    PyObject *im_unicode_tmp = NULL;

    /* Locale settings, either from the actual locale or
       from a hard-code pseudo-locale */
    LocaleInfo locale = STATIC_LOCALE_INFO_INIT;

    if (format->precision > INT_MAX) {
        PyErr_SetString(PyExc_ValueError, "precision too big");
        goto done;
    }
    precision = (int)format->precision;

    /* Zero padding is not allowed. */
    if (format->fill_char == '0') {
        PyErr_SetString(PyExc_ValueError,
                        "Zero padding is not allowed in complex format "
                        "specifier");
        goto done;
    }

    /* Neither is '=' alignment . */
    if (format->align == '=') {
        PyErr_SetString(PyExc_ValueError,
                        "'=' alignment flag is not allowed in complex format "
                        "specifier");
        goto done;
    }

    re = PyComplex_RealAsDouble(value);
    if (re == -1.0 && PyErr_Occurred())
        goto done;
    im = PyComplex_ImagAsDouble(value);
    if (im == -1.0 && PyErr_Occurred())
        goto done;

    if (format->alternate)
        flags |= Py_DTSF_ALT;

    if (type == '\0') {
        /* Omitted type specifier. Should be like str(self). */
        type = 'r';
        default_precision = 0;
        if (re == 0.0 && copysign(1.0, re) == 1.0)
            skip_re = 1;
        else
            add_parens = 1;
    }

    if (type == 'n')
        /* 'n' is the same as 'g', except for the locale used to
           format the result. We take care of that later. */
        type = 'g';

    if (precision < 0)
        precision = default_precision;
    else if (type == 'r')
        type = 'g';

    /* Cast "type", because if we're in unicode we need to pass a
       8-bit char. This is safe, because we've restricted what "type"
       can be. */
    re_buf = PyOS_double_to_string(re, (char)type, precision, flags,
                                   &re_float_type);
    if (re_buf == NULL)
        goto done;
    im_buf = PyOS_double_to_string(im, (char)type, precision, flags,
                                   &im_float_type);
    if (im_buf == NULL)
        goto done;

    n_re_digits = strlen(re_buf);
    n_im_digits = strlen(im_buf);

    /* Since there is no unicode version of PyOS_double_to_string,
       just use the 8 bit version and then convert to unicode. */
    re_unicode_tmp = _PyUnicode_FromASCII(re_buf, n_re_digits);
    if (re_unicode_tmp == NULL)
        goto done;
    i_re = 0;

    im_unicode_tmp = _PyUnicode_FromASCII(im_buf, n_im_digits);
    if (im_unicode_tmp == NULL)
        goto done;
    i_im = 0;

    /* Is a sign character present in the output?  If so, remember it
       and skip it */
    if (PyUnicode_READ_CHAR(re_unicode_tmp, i_re) == '-') {
        re_sign_char = '-';
        ++i_re;
        --n_re_digits;
    }
    if (PyUnicode_READ_CHAR(im_unicode_tmp, i_im) == '-') {
        im_sign_char = '-';
        ++i_im;
        --n_im_digits;
    }

    /* Determine if we have any "remainder" (after the digits, might include
       decimal or exponent or both (or neither)) */
    parse_number(re_unicode_tmp, i_re, i_re + n_re_digits,
                 &n_re_remainder, &re_has_decimal);
    parse_number(im_unicode_tmp, i_im, i_im + n_im_digits,
                 &n_im_remainder, &im_has_decimal);

    /* Determine the grouping, separator, and decimal point, if any. */
    if (get_locale_info(format->type == 'n' ? LT_CURRENT_LOCALE :
                        (format->thousands_separators ?
                         LT_DEFAULT_LOCALE :
                         LT_NO_LOCALE),
                        &locale) == -1)
        goto done;

    /* Turn off any padding. We'll do it later after we've composed
       the numbers without padding. */
    tmp_format.fill_char = '\0';
    tmp_format.align = '<';
    tmp_format.width = -1;

    /* Calculate how much memory we'll need. */
    n_re_total = calc_number_widths(&re_spec, 0, re_sign_char, re_unicode_tmp,
                                    i_re, i_re + n_re_digits, n_re_remainder,
                                    re_has_decimal, &locale, &tmp_format,
                                    &maxchar);

    /* Same formatting, but always include a sign, unless the real part is
     * going to be omitted, in which case we use whatever sign convention was
     * requested by the original format. */
    if (!skip_re)
        tmp_format.sign = '+';
    n_im_total = calc_number_widths(&im_spec, 0, im_sign_char, im_unicode_tmp,
                                    i_im, i_im + n_im_digits, n_im_remainder,
                                    im_has_decimal, &locale, &tmp_format,
                                    &maxchar);

    if (skip_re)
        n_re_total = 0;

    /* Add 1 for the 'j', and optionally 2 for parens. */
    calc_padding(n_re_total + n_im_total + 1 + add_parens * 2,
                 format->width, format->align, &lpad, &rpad, &total);

    if (lpad || rpad)
        maxchar = Py_MAX(maxchar, format->fill_char);

    if (_PyUnicodeWriter_Prepare(writer, total, maxchar) == -1)
        goto done;
    rkind = writer->kind;
    rdata = writer->data;

    /* Populate the memory. First, the padding. */
    result = fill_padding(writer,
                          n_re_total + n_im_total + 1 + add_parens * 2,
                          format->fill_char, lpad, rpad);
    if (result == -1)
        goto done;

    if (add_parens) {
        PyUnicode_WRITE(rkind, rdata, writer->pos, '(');
        writer->pos++;
    }

    if (!skip_re) {
        result = fill_number(writer, &re_spec,
                             re_unicode_tmp, i_re, i_re + n_re_digits,
                             NULL, 0,
                             0,
                             &locale, 0);
        if (result == -1)
            goto done;
    }
    result = fill_number(writer, &im_spec,
                         im_unicode_tmp, i_im, i_im + n_im_digits,
                         NULL, 0,
                         0,
                         &locale, 0);
    if (result == -1)
        goto done;
    PyUnicode_WRITE(rkind, rdata, writer->pos, 'j');
    writer->pos++;

    if (add_parens) {
        PyUnicode_WRITE(rkind, rdata, writer->pos, ')');
        writer->pos++;
    }

    writer->pos += rpad;

done:
    PyMem_Free(re_buf);
    PyMem_Free(im_buf);
    Py_XDECREF(re_unicode_tmp);
    Py_XDECREF(im_unicode_tmp);
    free_locale_info(&locale);
    return result;
}

/************************************************************************/
/*********** built in formatters ****************************************/
/************************************************************************/
static int
format_obj(PyObject *obj, _PyUnicodeWriter *writer)
{
    PyObject *str;
    int err;

    str = PyObject_Str(obj);
    if (str == NULL)
        return -1;
    err = _PyUnicodeWriter_WriteStr(writer, str);
    Py_DECREF(str);
    return err;
}

int
_PyUnicode_FormatAdvancedWriter(_PyUnicodeWriter *writer,
                                PyObject *obj,
                                PyObject *format_spec,
                                Py_ssize_t start, Py_ssize_t end)
{
    InternalFormatSpec format;

    assert(PyUnicode_Check(obj));

    /* check for the special case of zero length format spec, make
       it equivalent to str(obj) */
    if (start == end) {
        if (PyUnicode_CheckExact(obj))
            return _PyUnicodeWriter_WriteStr(writer, obj);
        else
            return format_obj(obj, writer);
    }

    /* parse the format_spec */
    if (!parse_internal_render_format_spec(format_spec, start, end,
                                           &format, 's', '<'))
        return -1;

    /* type conversion? */
    switch (format.type) {
    case 's':
        /* no type conversion needed, already a string.  do the formatting */
        return format_string_internal(obj, &format, writer);
    default:
        /* unknown */
        unknown_presentation_type(format.type, obj->ob_type->tp_name);
        return -1;
    }
}

int
_PyLong_FormatAdvancedWriter(_PyUnicodeWriter *writer,
                             PyObject *obj,
                             PyObject *format_spec,
                             Py_ssize_t start, Py_ssize_t end)
{
    PyObject *tmp = NULL, *str = NULL;
    InternalFormatSpec format;
    int result = -1;

    /* check for the special case of zero length format spec, make
       it equivalent to str(obj) */
    if (start == end) {
        if (PyLong_CheckExact(obj))
            return _PyLong_FormatWriter(writer, obj, 10, 0);
        else
            return format_obj(obj, writer);
    }

    /* parse the format_spec */
    if (!parse_internal_render_format_spec(format_spec, start, end,
                                           &format, 'd', '>'))
        goto done;

    /* type conversion? */
    switch (format.type) {
    case 'b':
    case 'c':
    case 'd':
    case 'o':
    case 'x':
    case 'X':
    case 'n':
        /* no type conversion needed, already an int.  do the formatting */
        result = format_long_internal(obj, &format, writer);
        break;

    case 'e':
    case 'E':
    case 'f':
    case 'F':
    case 'g':
    case 'G':
    case '%':
        /* convert to float */
        tmp = PyNumber_Float(obj);
        if (tmp == NULL)
            goto done;
        result = format_float_internal(tmp, &format, writer);
        break;

    default:
        /* unknown */
        unknown_presentation_type(format.type, obj->ob_type->tp_name);
        goto done;
    }

done:
    Py_XDECREF(tmp);
    Py_XDECREF(str);
    return result;
}

int
_PyFloat_FormatAdvancedWriter(_PyUnicodeWriter *writer,
                              PyObject *obj,
                              PyObject *format_spec,
                              Py_ssize_t start, Py_ssize_t end)
{
    InternalFormatSpec format;

    /* check for the special case of zero length format spec, make
       it equivalent to str(obj) */
    if (start == end)
        return format_obj(obj, writer);

    /* parse the format_spec */
    if (!parse_internal_render_format_spec(format_spec, start, end,
                                           &format, '\0', '>'))
        return -1;

    /* type conversion? */
    switch (format.type) {
    case '\0': /* No format code: like 'g', but with at least one decimal. */
    case 'e':
    case 'E':
    case 'f':
    case 'F':
    case 'g':
    case 'G':
    case 'n':
    case '%':
        /* no conversion, already a float.  do the formatting */
        return format_float_internal(obj, &format, writer);

    default:
        /* unknown */
        unknown_presentation_type(format.type, obj->ob_type->tp_name);
        return -1;
    }
}

int
_PyComplex_FormatAdvancedWriter(_PyUnicodeWriter *writer,
                                PyObject *obj,
                                PyObject *format_spec,
                                Py_ssize_t start, Py_ssize_t end)
{
    InternalFormatSpec format;

    /* check for the special case of zero length format spec, make
       it equivalent to str(obj) */
    if (start == end)
        return format_obj(obj, writer);

    /* parse the format_spec */
    if (!parse_internal_render_format_spec(format_spec, start, end,
                                           &format, '\0', '>'))
        return -1;

    /* type conversion? */
    switch (format.type) {
    case '\0': /* No format code: like 'g', but with at least one decimal. */
    case 'e':
    case 'E':
    case 'f':
    case 'F':
    case 'g':
    case 'G':
    case 'n':
        /* no conversion, already a complex.  do the formatting */
        return format_complex_internal(obj, &format, writer);

    default:
        /* unknown */
        unknown_presentation_type(format.type, obj->ob_type->tp_name);
        return -1;
    }
}
dcfExamplesRoot.ref = "examples.html"; dcfExamplesRoot.title = "Tutorial & Examples"; qSort(dcfExamplesRoot.subsections); DcfSection qtRoot; appendDcfSubSection(&qtRoot, dcfClassesRoot); appendDcfSubSection(&qtRoot, dcfOverviewsRoot); appendDcfSubSection(&qtRoot, dcfExamplesRoot); generateDcf(project.toLower().simplified().replace(" ", "-"), "index.html", projectDescription, qtRoot); generateDcf("designer", "designer-manual.html", "Qt Designer Manual", dcfDesignerRoot); generateDcf("linguist", "linguist-manual.html", "Qt Linguist Manual", dcfLinguistRoot); generateDcf("assistant", "assistant-manual.html", "Qt Assistant Manual", dcfAssistantRoot); generateDcf("qmake", "qmake-manual.html", "qmake Manual", dcfQmakeRoot); QString fileBase = project.toLower().simplified().replace(" ", "-"); generateIndex(fileBase, projectUrl, projectDescription); generatePageIndex(outputDir() + "/" + fileBase + ".pageindex", marker); helpProjectWriter->generate(myTree); } void HtmlGenerator::startText(const Node * /* relative */, CodeMarker * /* marker */) { inLink = false; inContents = false; inSectionHeading = false; inTableHeader = false; numTableRows = 0; threeColumnEnumValueTable = true; link.clear(); sectionNumber.clear(); } int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker) { int skipAhead = 0; static bool in_para = false; switch (atom->type()) { case Atom::AbstractLeft: break; case Atom::AbstractRight: break; case Atom::AutoLink: if (!inLink && !inContents && !inSectionHeading) { const Node *node = 0; QString link = getLink(atom, relative, marker, &node); if (!link.isEmpty()) { beginLink(link, node, relative, marker); generateLink(atom, relative, marker); endLink(); } else { out() << protectEnc(atom->string()); } } else { out() << protectEnc(atom->string()); } break; case Atom::BaseName: break; case Atom::BriefLeft: if (relative->type() == Node::Fake) { skipAhead = skipAtoms(atom, Atom::BriefRight); break; } out() << "<p>"; if (relative->type() == Node::Property || relative->type() == Node::Variable) { QString str; atom = atom->next(); while (atom != 0 && atom->type() != Atom::BriefRight) { if (atom->type() == Atom::String || atom->type() == Atom::AutoLink) str += atom->string(); skipAhead++; atom = atom->next(); } str[0] = str[0].toLower(); if (str.right(1) == ".") str.truncate(str.length() - 1); out() << "This "; if (relative->type() == Node::Property) out() << "property"; else out() << "variable"; QStringList words = str.split(" "); if (!(words.first() == "contains" || words.first() == "specifies" || words.first() == "describes" || words.first() == "defines" || words.first() == "holds" || words.first() == "determines")) out() << " holds "; else out() << " "; out() << str << "."; } break; case Atom::BriefRight: if (relative->type() != Node::Fake) out() << "</p>\n"; break; case Atom::C: out() << formattingLeftMap()[ATOM_FORMATTING_TELETYPE]; if (inLink) { out() << protectEnc(plainCode(atom->string())); } else { out() << highlightedCode(atom->string(), marker, relative); } out() << formattingRightMap()[ATOM_FORMATTING_TELETYPE]; break; case Atom::Code: out() << "<pre class=\"highlightedCode\">" << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()), marker,relative)) << "</pre>\n"; break; #ifdef QDOC_QML case Atom::Qml: out() << "<pre class=\"highlightedCode\">" << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()), marker,relative)) << "</pre>\n"; break; #endif case Atom::CodeNew: out() << "<p>you can rewrite it as</p>\n" << "<pre class=\"highlightedCode\">" << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()), marker,relative)) << "</pre>\n"; break; case Atom::CodeOld: out() << "<p>For example, if you have code like</p>\n"; // fallthrough case Atom::CodeBad: out() << "<pre class=\"highlightedCode\">" << trimmedTrailing(protectEnc(plainCode(indent(codeIndent,atom->string())))) << "</pre>\n"; break; case Atom::FootnoteLeft: // ### For now if (in_para) { out() << "</p>\n"; in_para = false; } out() << "<!-- "; break; case Atom::FootnoteRight: // ### For now out() << "-->"; break; case Atom::FormatElse: case Atom::FormatEndif: case Atom::FormatIf: break; case Atom::FormattingLeft: out() << formattingLeftMap()[atom->string()]; if (atom->string() == ATOM_FORMATTING_PARAMETER) { if (atom->next() != 0 && atom->next()->type() == Atom::String) { QRegExp subscriptRegExp("([a-z]+)_([0-9n])"); if (subscriptRegExp.exactMatch(atom->next()->string())) { out() << subscriptRegExp.cap(1) << "<sub>" << subscriptRegExp.cap(2) << "</sub>"; skipAhead = 1; } } } break; case Atom::FormattingRight: if (atom->string() == ATOM_FORMATTING_LINK) { endLink(); } else { out() << formattingRightMap()[atom->string()]; } break; case Atom::AnnotatedList: { QList<Node*> values = myTree->groups().values(atom->string()); NodeMap nodeMap; for (int i = 0; i < values.size(); ++i) { const Node* n = values.at(i); if ((n->status() != Node::Internal) && (n->access() != Node::Private)) { nodeMap.insert(n->nameForLists(),n); } } generateAnnotatedList(relative, marker, nodeMap); } break; case Atom::GeneratedList: if (atom->string() == "annotatedclasses") { generateAnnotatedList(relative, marker, nonCompatClasses); } else if (atom->string() == "classes") { generateCompactList(relative, marker, nonCompatClasses); } else if (atom->string().contains("classesbymodule")) { QString arg = atom->string().trimmed(); QString moduleName = atom->string().mid(atom->string().indexOf( "classesbymodule") + 15).trimmed(); if (moduleClassMap.contains(moduleName)) generateAnnotatedList(relative, marker, moduleClassMap[moduleName]); } else if (atom->string().contains("classesbyedition")) { QString arg = atom->string().trimmed(); QString editionName = atom->string().mid(atom->string().indexOf( "classesbyedition") + 16).trimmed(); if (editionModuleMap.contains(editionName)) { // Add all classes in the modules listed for that edition. NodeMap editionClasses; foreach (const QString &moduleName, editionModuleMap[editionName]) { if (moduleClassMap.contains(moduleName)) editionClasses.unite(moduleClassMap[moduleName]); } // Add additional groups and remove groups of classes that // should be excluded from the edition. QMultiMap <QString, Node *> groups = myTree->groups(); foreach (const QString &groupName, editionGroupMap[editionName]) { QList<Node *> groupClasses; if (groupName.startsWith("-")) { groupClasses = groups.values(groupName.mid(1)); foreach (const Node *node, groupClasses) editionClasses.remove(node->name()); } else { groupClasses = groups.values(groupName); foreach (const Node *node, groupClasses) editionClasses.insert(node->name(), node); } } generateAnnotatedList(relative, marker, editionClasses); } } else if (atom->string() == "classhierarchy") { generateClassHierarchy(relative, marker, nonCompatClasses); } else if (atom->string() == "compatclasses") { generateCompactList(relative, marker, compatClasses); } else if (atom->string() == "obsoleteclasses") { generateCompactList(relative, marker, obsoleteClasses); } else if (atom->string() == "functionindex") { generateFunctionIndex(relative, marker); } else if (atom->string() == "legalese") { generateLegaleseList(relative, marker); } else if (atom->string() == "mainclasses") { generateCompactList(relative, marker, mainClasses); } else if (atom->string() == "services") { generateCompactList(relative, marker, serviceClasses); } else if (atom->string() == "overviews") { generateOverviewList(relative, marker); } else if (atom->string() == "namespaces") { generateAnnotatedList(relative, marker, namespaceIndex); } else if (atom->string() == "related") { const FakeNode *fake = static_cast<const FakeNode *>(relative); if (fake && !fake->groupMembers().isEmpty()) { NodeMap groupMembersMap; foreach (const Node *node, fake->groupMembers()) { if (node->type() == Node::Fake) groupMembersMap[fullName(node, relative, marker)] = node; } generateAnnotatedList(fake, marker, groupMembersMap); } } else if (atom->string() == "relatedinline") { const FakeNode *fake = static_cast<const FakeNode *>(relative); if (fake && !fake->groupMembers().isEmpty()) { // Reverse the list into the original scan order. // Should be sorted. But on what? It may not be a // regular class or page definition. QList<const Node *> list; foreach (const Node *node, fake->groupMembers()) list.prepend(node); foreach (const Node *node, list) generateBody(node, marker); } } break; case Atom::SinceList: { NewSinceMaps::const_iterator nsmap; nsmap = newSinceMaps.find(atom->string()); NewClassMaps::const_iterator ncmap; ncmap = newClassMaps.find(atom->string()); NewClassMaps::const_iterator nqcmap; nqcmap = newQmlClassMaps.find(atom->string()); if ((nsmap != newSinceMaps.constEnd()) && !nsmap.value().isEmpty()) { QList<Section> sections; QList<Section>::ConstIterator s; for (int i=0; i<LastSinceType; ++i) sections.append(Section(sinceTitle(i),QString(),QString(),QString())); NodeMultiMap::const_iterator n = nsmap.value().constBegin(); while (n != nsmap.value().constEnd()) { const Node* node = n.value(); switch (node->type()) { case Node::Fake: if (node->subType() == Node::QmlClass) { sections[QmlClass].appendMember((Node*)node); } break; case Node::Namespace: sections[Namespace].appendMember((Node*)node); break; case Node::Class: sections[Class].appendMember((Node*)node); break; case Node::Enum: sections[Enum].appendMember((Node*)node); break; case Node::Typedef: sections[Typedef].appendMember((Node*)node); break; case Node::Function: { const FunctionNode* fn = static_cast<const FunctionNode*>(node); if (fn->isMacro()) sections[Macro].appendMember((Node*)node); else { Node* p = fn->parent(); if (p) { if (p->type() == Node::Class) sections[MemberFunction].appendMember((Node*)node); else if (p->type() == Node::Namespace) { if (p->name().isEmpty()) sections[GlobalFunction].appendMember((Node*)node); else sections[NamespaceFunction].appendMember((Node*)node); } else sections[GlobalFunction].appendMember((Node*)node); } else sections[GlobalFunction].appendMember((Node*)node); } break; } case Node::Property: sections[Property].appendMember((Node*)node); break; case Node::Variable: sections[Variable].appendMember((Node*)node); break; case Node::QmlProperty: sections[QmlProperty].appendMember((Node*)node); break; case Node::QmlSignal: sections[QmlSignal].appendMember((Node*)node); break; case Node::QmlMethod: sections[QmlMethod].appendMember((Node*)node); break; default: break; } ++n; } /* First generate the table of contents. */ out() << "<ul>\n"; s = sections.constBegin(); while (s != sections.constEnd()) { if (!(*s).members.isEmpty()) { out() << "<li>" << "<a href=\"#" << Doc::canonicalTitle((*s).name) << "\">" << (*s).name << "</a></li>\n"; } ++s; } out() << "</ul>\n"; int idx = 0; s = sections.constBegin(); while (s != sections.constEnd()) { if (!(*s).members.isEmpty()) { out() << "<a name=\"" << Doc::canonicalTitle((*s).name) << "\"></a>\n"; out() << "<h3>" << protectEnc((*s).name) << "</h3>\n"; if (idx == Class) generateCompactList(0, marker, ncmap.value(), QString("Q")); else if (idx == QmlClass) generateCompactList(0, marker, nqcmap.value(), QString("Q")); else if (idx == MemberFunction) { ParentMaps parentmaps; ParentMaps::iterator pmap; NodeList::const_iterator i = s->members.constBegin(); while (i != s->members.constEnd()) { Node* p = (*i)->parent(); pmap = parentmaps.find(p); if (pmap == parentmaps.end()) pmap = parentmaps.insert(p,NodeMultiMap()); pmap->insert((*i)->name(),(*i)); ++i; } pmap = parentmaps.begin(); while (pmap != parentmaps.end()) { NodeList nlist = pmap->values(); out() << "<p>Class "; out() << "<a href=\"" << linkForNode(pmap.key(), 0) << "\">"; QStringList pieces = fullName(pmap.key(), 0, marker).split("::"); out() << protectEnc(pieces.last()); out() << "</a>" << ":</p>\n"; generateSection(nlist, 0, marker, CodeMarker::Summary); out() << "<br />"; ++pmap; } } else generateSection(s->members, 0, marker, CodeMarker::Summary); } ++idx; ++s; } } } break; case Atom::Image: case Atom::InlineImage: { QString fileName = imageFileName(relative, atom->string()); QString text; if (atom->next() != 0) text = atom->next()->string(); if (atom->type() == Atom::Image) out() << "<p class=\"centerAlign\">"; if (fileName.isEmpty()) { out() << "<font color=\"red\">[Missing image " << protectEnc(atom->string()) << "]</font>"; } else { out() << "<img src=\"" << protectEnc(fileName) << "\""; if (!text.isEmpty()) out() << " alt=\"" << protectEnc(text) << "\""; out() << " />"; helpProjectWriter->addExtraFile(fileName); } if (atom->type() == Atom::Image) out() << "</p>"; } break; case Atom::ImageText: break; case Atom::LegaleseLeft: out() << "<div class=\"LegaleseLeft\">"; break; case Atom::LegaleseRight: out() << "</div>"; break; case Atom::LineBreak: out() << "<br />"; break; case Atom::Link: { const Node *node = 0; QString myLink = getLink(atom, relative, marker, &node); if (myLink.isEmpty()) { relative->doc().location().warning(tr("Cannot link to '%1' in %2") .arg(atom->string()) .arg(marker->plainFullName(relative))); } beginLink(myLink, node, relative, marker); skipAhead = 1; } break; case Atom::LinkNode: { const Node *node = CodeMarker::nodeForString(atom->string()); beginLink(linkForNode(node, relative), node, relative, marker); skipAhead = 1; } break; case Atom::ListLeft: if (in_para) { out() << "</p>\n"; in_para = false; } if (atom->string() == ATOM_LIST_BULLET) { out() << "<ul>\n"; } else if (atom->string() == ATOM_LIST_TAG) { out() << "<dl>\n"; } else if (atom->string() == ATOM_LIST_VALUE) { threeColumnEnumValueTable = isThreeColumnEnumValueTable(atom); if (threeColumnEnumValueTable) { out() << "<table class=\"valuelist\">" << "<tr><th>Constant</th>" << "<th>Value</th>" << "<th>Description</th></tr>\n"; } else { out() << "<table class=\"valuelist\">" << "<tr><th>Constant</th><th>Value</th></tr>\n"; } } else { out() << "<ol type="; if (atom->string() == ATOM_LIST_UPPERALPHA) { out() << "\"A\""; } else if (atom->string() == ATOM_LIST_LOWERALPHA) { out() << "\"a\""; } else if (atom->string() == ATOM_LIST_UPPERROMAN) { out() << "\"I\""; } else if (atom->string() == ATOM_LIST_LOWERROMAN) { out() << "\"i\""; } else { // (atom->string() == ATOM_LIST_NUMERIC) out() << "\"1\""; } if (atom->next() != 0 && atom->next()->string().toInt() != 1) out() << " start=\"" << atom->next()->string() << "\""; out() << ">\n"; } break; case Atom::ListItemNumber: break; case Atom::ListTagLeft: if (atom->string() == ATOM_LIST_TAG) { out() << "<dt>"; } else { // (atom->string() == ATOM_LIST_VALUE) // ### Trenton out() << "<tr><td class=\"topAlign\"><tt>" << protectEnc(plainCode(marker->markedUpEnumValue(atom->next()->string(), relative))) << "</tt></td><td class=\"centerAlign topAlign\">"; QString itemValue; if (relative->type() == Node::Enum) { const EnumNode *enume = static_cast<const EnumNode *>(relative); itemValue = enume->itemValue(atom->next()->string()); } if (itemValue.isEmpty()) out() << "?"; else out() << "<tt>" << protectEnc(itemValue) << "</tt>"; skipAhead = 1; } break; case Atom::ListTagRight: if (atom->string() == ATOM_LIST_TAG) out() << "</dt>\n"; break; case Atom::ListItemLeft: if (atom->string() == ATOM_LIST_TAG) { out() << "<dd>"; } else if (atom->string() == ATOM_LIST_VALUE) { if (threeColumnEnumValueTable) { out() << "</td><td class=\"topAlign\">"; if (matchAhead(atom, Atom::ListItemRight)) out() << "&nbsp;"; } } else { out() << "<li>"; } if (matchAhead(atom, Atom::ParaLeft)) skipAhead = 1; break; case Atom::ListItemRight: if (atom->string() == ATOM_LIST_TAG) { out() << "</dd>\n"; } else if (atom->string() == ATOM_LIST_VALUE) { out() << "</td></tr>\n"; } else { out() << "</li>\n"; } break; case Atom::ListRight: if (atom->string() == ATOM_LIST_BULLET) { out() << "</ul>\n"; } else if (atom->string() == ATOM_LIST_TAG) { out() << "</dl>\n"; } else if (atom->string() == ATOM_LIST_VALUE) { out() << "</table>\n"; } else { out() << "</ol>\n"; } break; case Atom::Nop: break; case Atom::ParaLeft: out() << "<p>"; in_para = true; break; case Atom::ParaRight: endLink(); if (in_para) { out() << "</p>\n"; in_para = false; } //if (!matchAhead(atom, Atom::ListItemRight) && !matchAhead(atom, Atom::TableItemRight)) // out() << "</p>\n"; break; case Atom::QuotationLeft: out() << "<blockquote>"; break; case Atom::QuotationRight: out() << "</blockquote>\n"; break; case Atom::RawString: out() << atom->string(); break; case Atom::SectionLeft: #if 0 { int nextLevel = atom->string().toInt(); if (sectionNumber.size() < nextLevel) { do { sectionNumber.append("1"); } while (sectionNumber.size() < nextLevel); } else { while (sectionNumber.size() > nextLevel) { sectionNumber.removeLast(); } sectionNumber.last() = QString::number(sectionNumber.last().toInt() + 1); } out() << "<a name=\"sec-" << sectionNumber.join("-") << "\"></a>\n"; } #else out() << "<a name=\"" << Doc::canonicalTitle(Text::sectionHeading(atom).toString()) << "\"></a>\n"; #endif break; case Atom::SectionRight: break; case Atom::SectionHeadingLeft: out() << "<h" + QString::number(atom->string().toInt() + hOffset(relative)) + ">"; inSectionHeading = true; break; case Atom::SectionHeadingRight: out() << "</h" + QString::number(atom->string().toInt() + hOffset(relative)) + ">\n"; inSectionHeading = false; break; case Atom::SidebarLeft: break; case Atom::SidebarRight: break; case Atom::String: if (inLink && !inContents && !inSectionHeading) { generateLink(atom, relative, marker); } else { out() << protectEnc(atom->string()); } break; case Atom::TableLeft: if (in_para) { out() << "</p>\n"; in_para = false; } if (!atom->string().isEmpty()) { if (atom->string().contains("%")) out() << "<table class=\"generic centerAlign\" width=\"" << atom->string() << "\">\n "; else { out() << "<table class=\"generic centerAlign\">\n"; } } else { out() << "<table class=\"generic centerAlign\">\n"; } numTableRows = 0; break; case Atom::TableRight: out() << "</table>\n"; break; case Atom::TableHeaderLeft: out() << "<thead><tr class=\"qt-style topAlign\">"; inTableHeader = true; break; case Atom::TableHeaderRight: out() << "</tr>"; if (matchAhead(atom, Atom::TableHeaderLeft)) { skipAhead = 1; out() << "\n<tr class=\"qt-style topAlign\">"; } else { out() << "</thead>\n"; inTableHeader = false; } break; case Atom::TableRowLeft: if (++numTableRows % 2 == 1) out() << "<tr class=\"odd topAlign\">"; else out() << "<tr class=\"even topAlign\">"; break; case Atom::TableRowRight: out() << "</tr>\n"; break; case Atom::TableItemLeft: { if (inTableHeader) out() << "<th"; else out() << "<td"; QStringList spans = atom->string().split(","); if (spans.size() == 2) { if (spans.at(0) != "1") out() << " colspan=\"" << spans.at(0) << "\""; if (spans.at(1) != "1") out() << " rowspan=\"" << spans.at(1) << "\""; out() << ">"; } if (matchAhead(atom, Atom::ParaLeft)) skipAhead = 1; } break; case Atom::TableItemRight: if (inTableHeader) out() << "</th>"; else out() << "</td>"; if (matchAhead(atom, Atom::ParaLeft)) skipAhead = 1; break; case Atom::TableOfContents: { int numColumns = 1; const Node *node = relative; Doc::SectioningUnit sectioningUnit = Doc::Section4; QStringList params = atom->string().split(","); QString columnText = params.at(0); QStringList pieces = columnText.split(" ", QString::SkipEmptyParts); if (pieces.size() >= 2) { columnText = pieces.at(0); pieces.pop_front(); QString path = pieces.join(" ").trimmed(); node = findNodeForTarget(path, relative, marker, atom); } if (params.size() == 2) { numColumns = qMax(columnText.toInt(), numColumns); sectioningUnit = (Doc::SectioningUnit)params.at(1).toInt(); } if (node) generateTableOfContents(node, marker, sectioningUnit, numColumns, relative); } break; case Atom::Target: out() << "<a name=\"" << Doc::canonicalTitle(atom->string()) << "\"></a>"; break; case Atom::UnhandledFormat: out() << "<b class=\"redFont\">&lt;Missing HTML&gt;</b>"; break; case Atom::UnknownCommand: out() << "<b class=\"redFont\"><code>\\" << protectEnc(atom->string()) << "</code></b>"; break; #ifdef QDOC_QML case Atom::QmlText: case Atom::EndQmlText: // don't do anything with these. They are just tags. break; #endif default: unknownAtom(atom); } return skipAhead; } void HtmlGenerator::generateClassLikeNode(const InnerNode *inner, CodeMarker *marker) { QList<Section> sections; QList<Section>::ConstIterator s; const ClassNode *classe = 0; const NamespaceNode *namespasse = 0; QString title; QString rawTitle; QString fullTitle; if (inner->type() == Node::Namespace) { namespasse = static_cast<const NamespaceNode *>(inner); rawTitle = marker->plainName(inner); fullTitle = marker->plainFullName(inner); title = rawTitle + " Namespace Reference"; } else if (inner->type() == Node::Class) { classe = static_cast<const ClassNode *>(inner); rawTitle = marker->plainName(inner); fullTitle = marker->plainFullName(inner); title = rawTitle + " Class Reference"; } DcfSection classSection; classSection.title = title; classSection.ref = linkForNode(inner, 0); classSection.keywords += qMakePair(inner->name(), classSection.ref); Text subtitleText; if (rawTitle != fullTitle) subtitleText << "(" << Atom(Atom::AutoLink, fullTitle) << ")" << Atom(Atom::LineBreak); QString fixedModule = inner->moduleName(); if (fixedModule == "Qt3SupportLight") fixedModule = "Qt3Support"; if (!fixedModule.isEmpty()) subtitleText << "[" << Atom(Atom::AutoLink, fixedModule) << " module]"; if (fixedModule.isEmpty()) { QMultiMap<QString, QString> publicGroups = myTree->publicGroups(); QList<QString> groupNames = publicGroups.values(inner->name()); if (!groupNames.isEmpty()) { qSort(groupNames.begin(), groupNames.end()); subtitleText << "["; for (int j=0; j<groupNames.count(); j++) { subtitleText << Atom(Atom::AutoLink, groupNames[j]); if (j<groupNames.count()-1) subtitleText <<", "; } subtitleText << "]"; } } generateHeader(title, inner, marker, true); sections = marker->sections(inner, CodeMarker::Summary, CodeMarker::Okay); generateTableOfContents(inner,marker,&sections); generateTitle(title, subtitleText, SmallSubTitle, inner, marker); #ifdef QDOC_QML if (classe && !classe->qmlElement().isEmpty()) { generateInstantiatedBy(classe,marker); } #endif generateBrief(inner, marker); generateIncludes(inner, marker); generateStatus(inner, marker); if (classe) { generateInherits(classe, marker); generateInheritedBy(classe, marker); } generateThreadSafeness(inner, marker); generateSince(inner, marker); out() << "<ul>\n"; QString membersLink = generateListOfAllMemberFile(inner, marker); if (!membersLink.isEmpty()) out() << "<li><a href=\"" << membersLink << "\">" << "List of all members, including inherited members</a></li>\n"; QString obsoleteLink = generateLowStatusMemberFile(inner, marker, CodeMarker::Obsolete); if (!obsoleteLink.isEmpty()) out() << "<li><a href=\"" << obsoleteLink << "\">" << "Obsolete members</a></li>\n"; QString compatLink = generateLowStatusMemberFile(inner, marker, CodeMarker::Compat); if (!compatLink.isEmpty()) out() << "<li><a href=\"" << compatLink << "\">" << "Qt 3 support members</a></li>\n"; out() << "</ul>\n"; bool needOtherSection = false; /* sections is built above for the call to generateTableOfContents(). */ s = sections.begin(); while (s != sections.end()) { if (s->members.isEmpty() && s->reimpMembers.isEmpty()) { if (!s->inherited.isEmpty()) needOtherSection = true; } else { if (!s->members.isEmpty()) { out() << "<hr />\n"; out() << "<a name=\"" << registerRef((*s).name.toLower()) << "\"></a>\n"; out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; generateSection(s->members, inner, marker, CodeMarker::Summary); } if (!s->reimpMembers.isEmpty()) { QString name = QString("Reimplemented ") + (*s).name; out() << "<hr />\n"; out() << "<a name=\"" << registerRef(name.toLower()) << "\"></a>\n"; out() << "<h2>" << protectEnc(name) << "</h2>\n"; generateSection(s->reimpMembers, inner, marker, CodeMarker::Summary); } if (!s->inherited.isEmpty()) { out() << "<ul>\n"; generateSectionInheritedList(*s, inner, marker, true); out() << "</ul>\n"; } } ++s; } if (needOtherSection) { out() << "<h3>Additional Inherited Members</h3>\n" "<ul>\n"; s = sections.begin(); while (s != sections.end()) { if (s->members.isEmpty() && !s->inherited.isEmpty()) generateSectionInheritedList(*s, inner, marker); ++s; } out() << "</ul>\n"; } out() << "<a name=\"" << registerRef("details") << "\"></a>\n"; if (!inner->doc().isEmpty()) { out() << "<hr />\n" << "<div class=\"descr\"/>\n" // QTBUG-9504 << "<h2>" << "Detailed Description" << "</h2>\n"; generateBody(inner, marker); out() << "</div>\n"; // QTBUG-9504 generateAlsoList(inner, marker); } sections = marker->sections(inner, CodeMarker::Detailed, CodeMarker::Okay); s = sections.begin(); while (s != sections.end()) { out() << "<hr />\n"; if (!(*s).divClass.isEmpty()) out() << "<div class=\"" << (*s).divClass << "\"/>\n"; // QTBUG-9504 out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; NodeList::ConstIterator m = (*s).members.begin(); while (m != (*s).members.end()) { if ((*m)->access() != Node::Private) { // ### check necessary? if ((*m)->type() != Node::Class) generateDetailedMember(*m, inner, marker); else { out() << "<h3> class "; generateFullName(*m, inner, marker); out() << "</h3>"; generateBrief(*m, marker, inner); } QStringList names; names << (*m)->name(); if ((*m)->type() == Node::Function) { const FunctionNode *func = reinterpret_cast<const FunctionNode *>(*m); if (func->metaness() == FunctionNode::Ctor || func->metaness() == FunctionNode::Dtor || func->overloadNumber() != 1) names.clear(); } else if ((*m)->type() == Node::Property) { const PropertyNode *prop = reinterpret_cast<const PropertyNode *>(*m); if (!prop->getters().isEmpty() && !names.contains(prop->getters().first()->name())) names << prop->getters().first()->name(); if (!prop->setters().isEmpty()) names << prop->setters().first()->name(); if (!prop->resetters().isEmpty()) names << prop->resetters().first()->name(); } else if ((*m)->type() == Node::Enum) { const EnumNode *enume = reinterpret_cast<const EnumNode*>(*m); if (enume->flagsType()) names << enume->flagsType()->name(); foreach (const QString &enumName, enume->doc().enumItemNames().toSet() - enume->doc().omitEnumItemNames().toSet()) names << plainCode(marker->markedUpEnumValue(enumName, enume)); } foreach (const QString &name, names) classSection.keywords += qMakePair(name,linkForNode(*m,0)); } ++m; } if (!(*s).divClass.isEmpty()) out() << "</div>\n"; // QTBUG-9504 ++s; } generateFooter(inner); if (!membersLink.isEmpty()) { DcfSection membersSection; membersSection.title = "List of all members"; membersSection.ref = membersLink; appendDcfSubSection(&classSection, membersSection); } if (!obsoleteLink.isEmpty()) { DcfSection obsoleteSection; obsoleteSection.title = "Obsolete members"; obsoleteSection.ref = obsoleteLink; appendDcfSubSection(&classSection, obsoleteSection); } if (!compatLink.isEmpty()) { DcfSection compatSection; compatSection.title = "Qt 3 support members"; compatSection.ref = compatLink; appendDcfSubSection(&classSection, compatSection); } appendDcfSubSection(&dcfClassesRoot, classSection); } void HtmlGenerator::generateFakeNode(const FakeNode *fake, CodeMarker *marker) { SubTitleSize subTitleSize = LargeSubTitle; DcfSection fakeSection; fakeSection.title = fake->fullTitle(); fakeSection.ref = linkForNode(fake, 0); QList<Section> sections; QList<Section>::const_iterator s; QString fullTitle = fake->fullTitle(); QString htmlTitle = fullTitle; if (fake->subType() == Node::File && !fake->subTitle().isEmpty()) { subTitleSize = SmallSubTitle; htmlTitle += " (" + fake->subTitle() + ")"; } else if (fake->subType() == Node::QmlBasicType) { fullTitle = "QML Basic Type: " + fullTitle; htmlTitle = fullTitle; } generateHeader(htmlTitle, fake, marker, true); /* Generate the TOC for the new doc format. Don't generate a TOC for the home page. */ if (fake->name() != QString("index.html")) generateTableOfContents(fake,marker,0); generateTitle(fullTitle, Text() << fake->subTitle(), subTitleSize, fake, marker); if (fake->subType() == Node::Module) { // Generate brief text and status for modules. generateBrief(fake, marker); generateStatus(fake, marker); if (moduleNamespaceMap.contains(fake->name())) { out() << "<a name=\"" << registerRef("namespaces") << "\"></a>\n"; out() << "<h2>Namespaces</h2>\n"; generateAnnotatedList(fake, marker, moduleNamespaceMap[fake->name()]); } if (moduleClassMap.contains(fake->name())) { out() << "<a name=\"" << registerRef("classes") << "\"></a>\n"; out() << "<h2>Classes</h2>\n"; generateAnnotatedList(fake, marker, moduleClassMap[fake->name()]); } } else if (fake->subType() == Node::HeaderFile) { // Generate brief text and status for modules. generateBrief(fake, marker); generateStatus(fake, marker); out() << "<ul>\n"; QString membersLink = generateListOfAllMemberFile(fake, marker); if (!membersLink.isEmpty()) out() << "<li><a href=\"" << membersLink << "\">" << "List of all members, including inherited members</a></li>\n"; QString obsoleteLink = generateLowStatusMemberFile(fake, marker, CodeMarker::Obsolete); if (!obsoleteLink.isEmpty()) out() << "<li><a href=\"" << obsoleteLink << "\">" << "Obsolete members</a></li>\n"; QString compatLink = generateLowStatusMemberFile(fake, marker, CodeMarker::Compat); if (!compatLink.isEmpty()) out() << "<li><a href=\"" << compatLink << "\">" << "Qt 3 support members</a></li>\n"; out() << "</ul>\n"; if (!membersLink.isEmpty()) { DcfSection membersSection; membersSection.title = "List of all members"; membersSection.ref = membersLink; appendDcfSubSection(&fakeSection, membersSection); } if (!obsoleteLink.isEmpty()) { DcfSection obsoleteSection; obsoleteSection.title = "Obsolete members"; obsoleteSection.ref = obsoleteLink; appendDcfSubSection(&fakeSection, obsoleteSection); } if (!compatLink.isEmpty()) { DcfSection compatSection; compatSection.title = "Qt 3 support members"; compatSection.ref = compatLink; appendDcfSubSection(&fakeSection, compatSection); } } #ifdef QDOC_QML else if (fake->subType() == Node::QmlClass) { const QmlClassNode* qml_cn = static_cast<const QmlClassNode*>(fake); const ClassNode* cn = qml_cn->classNode(); generateQmlInherits(qml_cn, marker); generateQmlInstantiates(qml_cn, marker); generateBrief(qml_cn, marker); generateQmlInheritedBy(qml_cn, marker); sections = marker->qmlSections(qml_cn,CodeMarker::Summary); s = sections.begin(); while (s != sections.end()) { out() << "<a name=\"" << registerRef((*s).name) << "\"></a>\n"; out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; generateQmlSummary(*s,fake,marker); ++s; } out() << "<a name=\"" << registerRef("details") << "\"></a>\n"; out() << "<h2>" << "Detailed Description" << "</h2>\n"; generateBody(fake, marker); if (cn) generateQmlText(cn->doc().body(), cn, marker, fake->name()); generateAlsoList(fake, marker); out() << "<hr />\n"; sections = marker->qmlSections(qml_cn,CodeMarker::Detailed); s = sections.begin(); while (s != sections.end()) { out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; NodeList::ConstIterator m = (*s).members.begin(); while (m != (*s).members.end()) { generateDetailedQmlMember(*m, fake, marker); out() << "<br />\n"; fakeSection.keywords += qMakePair((*m)->name(), linkForNode(*m,0)); ++m; } ++s; } generateFooter(fake); return; } #endif sections = marker->sections(fake, CodeMarker::Summary, CodeMarker::Okay); s = sections.begin(); while (s != sections.end()) { out() << "<a name=\"" << registerRef((*s).name) << "\"></a>\n"; out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; generateSectionList(*s, fake, marker, CodeMarker::Summary); ++s; } Text brief = fake->doc().briefText(); if (fake->subType() == Node::Module && !brief.isEmpty()) { out() << "<a name=\"" << registerRef("details") << "\"></a>\n"; out() << "<div class=\"descr\"/>\n"; // QTBUG-9504 out() << "<h2>" << "Detailed Description" << "</h2>\n"; } else out() << "<div class=\"descr\"/>\n"; // QTBUG-9504 generateBody(fake, marker); out() << "</div>\n"; // QTBUG-9504 generateAlsoList(fake, marker); if (!fake->groupMembers().isEmpty()) { NodeMap groupMembersMap; foreach (const Node *node, fake->groupMembers()) { if (node->type() == Node::Class || node->type() == Node::Namespace) groupMembersMap[node->name()] = node; } generateAnnotatedList(fake, marker, groupMembersMap); } fakeSection.keywords += qMakePair(fakeSection.title, fakeSection.ref); sections = marker->sections(fake, CodeMarker::Detailed, CodeMarker::Okay); s = sections.begin(); while (s != sections.end()) { out() << "<hr />\n"; out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; NodeList::ConstIterator m = (*s).members.begin(); while (m != (*s).members.end()) { generateDetailedMember(*m, fake, marker); fakeSection.keywords += qMakePair((*m)->name(), linkForNode(*m, 0)); ++m; } ++s; } generateFooter(fake); if (fake->subType() == Node::Example) { appendDcfSubSection(&dcfExamplesRoot, fakeSection); } else if (fake->subType() != Node::File) { QString contentsPage = fake->links().value(Node::ContentsLink).first; if (contentsPage == "Qt Designer Manual") { appendDcfSubSection(&dcfDesignerRoot, fakeSection); } else if (contentsPage == "Qt Linguist Manual") { appendDcfSubSection(&dcfLinguistRoot, fakeSection); } else if (contentsPage == "Qt Assistant Manual") { appendDcfSubSection(&dcfAssistantRoot, fakeSection); } else if (contentsPage == "qmake Manual") { appendDcfSubSection(&dcfQmakeRoot, fakeSection); } else { appendDcfSubSection(&dcfOverviewsRoot, fakeSection); } } } QString HtmlGenerator::fileExtension(const Node * /* node */) const { return "html"; } #if 0 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Qt Reference Documentation</title> <link rel="stylesheet" type="text/css" href="style/style.css" /> <!--[if IE]> <meta name="MSSmartTagsPreventParsing" content="true"> <meta http-equiv="imagetoolbar" content="no"> <![endif]--> <!--[if lt IE 7]> <link rel="stylesheet" type="text/css" href="style/style_ie6.css"> <![endif]--> <!--[if IE 7]> <link rel="stylesheet" type="text/css" href="style/style_ie7.css"> <![endif]--> <!--[if IE 8]> <link rel="stylesheet" type="text/css" href="style/style_ie8.css"> <![endif]--> <script src="scripts/jquery.js" type="text/javascript"></script> </head> #endif void HtmlGenerator::generateHeader(const QString& title, const Node *node, CodeMarker *marker, bool mainPage) { out() << QString("<?xml version=\"1.0\" encoding=\"%1\"?>\n").arg(outputEncoding); out() << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"; out() << "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"; out() << "<head>\n"; out() << " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"; QString shortVersion; shortVersion = project + " " + shortVersion + ": "; if (node && !node->doc().location().isEmpty()) out() << "<!-- " << node->doc().location().fileName() << " -->\n"; shortVersion = myTree->version(); if (shortVersion.count(QChar('.')) == 2) shortVersion.truncate(shortVersion.lastIndexOf(QChar('.'))); if (!shortVersion.isEmpty()) { if (project == "QSA") shortVersion = "QSA " + shortVersion + ": "; else shortVersion = "Qt " + shortVersion + ": "; } out() << " <title>" << shortVersion << protectEnc(title) << "</title>\n"; //out() << " <title>Qt Reference Documentation</title>"; out() << " <link rel=\"stylesheet\" type=\"text/css\" href=\"style/style.css\" />\n"; out() << " <!--[if IE]>\n"; out() << " <meta name=\"MSSmartTagsPreventParsing\" content=\"true\">\n"; out() << " <meta http-equiv=\"imagetoolbar\" content=\"no\">\n"; out() << " <![endif]-->\n"; out() << " <!--[if lt IE 7]>\n"; out() << " <link rel=\"stylesheet\" type=\"text/css\" href=\"style/style_ie6.css\">\n"; out() << " <![endif]-->\n"; out() << " <!--[if IE 7]>\n"; out() << " <link rel=\"stylesheet\" type=\"text/css\" href=\"style/style_ie7.css\">\n"; out() << " <![endif]-->\n"; out() << " <!--[if IE 8]>\n"; out() << " <link rel=\"stylesheet\" type=\"text/css\" href=\"style/style_ie8.css\">\n"; out() << " <![endif]-->\n"; out() << " <script src=\"scripts/jquery.js\" type=\"text/javascript\"></script>\n"; out() << " <script src=\"scripts/functions.js\" type=\"text/javascript\"></script>\n"; out() << "</head>\n"; if (offlineDocs) out() << "<body class=\"offline\">\n"; else out() << "<body class=\"\">\n"; if (mainPage) generateMacRef(node, marker); out() << QString(postHeader).replace("\\" + COMMAND_VERSION, myTree->version()); #if 0 // Removed for new docf format. MWS if (node && !node->links().empty()) out() << "<p>\n" << navigationLinks << "</p>\n"; #endif } void HtmlGenerator::generateTitle(const QString& title, const Text &subTitle, SubTitleSize subTitleSize, const Node *relative, CodeMarker *marker) { if (!title.isEmpty()) out() << "<h1 class=\"title\">" << protectEnc(title); if (!subTitle.isEmpty()) { out() << "<br />"; if (subTitleSize == SmallSubTitle) out() << "<span class=\"small-subtitle\">"; else out() << "<span class=\"subtitle\">"; generateText(subTitle, relative, marker); out() << "</span>\n"; } if (!title.isEmpty()) out() << "</h1>\n"; } void HtmlGenerator::generateFooter(const Node *node) { if (node && !node->links().empty()) out() << "<p>\n" << navigationLinks << "</p>\n"; out() << QString(footer).replace("\\" + COMMAND_VERSION, myTree->version()) << QString(address).replace("\\" + COMMAND_VERSION, myTree->version()) << "</body>\n" "</html>\n"; } void HtmlGenerator::generateBrief(const Node *node, CodeMarker *marker, const Node *relative) { Text brief = node->doc().briefText(); if (!brief.isEmpty()) { out() << "<p>"; generateText(brief, node, marker); if (!relative || node == relative) out() << " <a href=\"#"; else out() << " <a href=\"" << linkForNode(node, relative) << "#"; out() << registerRef("details") << "\">More...</a></p>\n"; } } void HtmlGenerator::generateIncludes(const InnerNode *inner, CodeMarker *marker) { if (!inner->includes().isEmpty()) { out() << "<pre clas=\"highlightedCode\">" << trimmedTrailing(highlightedCode(indent(codeIndent, marker->markedUpIncludes(inner->includes())), marker,inner)) << "</pre>"; } } /*! Generates a table of contents begining at \a node. */ void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker, Doc::SectioningUnit sectioningUnit, int numColumns, const Node *relative) { return; if (!node->doc().hasTableOfContents()) return; QList<Atom *> toc = node->doc().tableOfContents(); if (toc.isEmpty()) return; QString nodeName = ""; if (node != relative) nodeName = node->name(); QStringList sectionNumber; int columnSize = 0; QString tdTag; if (numColumns > 1) { tdTag = "<td>"; /* width=\"" + QString::number((100 + numColumns - 1) / numColumns) + "%\">";*/ out() << "<table class=\"toc\">\n<tr class=\"topAlign\">" << tdTag << "\n"; } // disable nested links in table of contents inContents = true; inLink = true; for (int i = 0; i < toc.size(); ++i) { Atom *atom = toc.at(i); int nextLevel = atom->string().toInt(); if (nextLevel > (int)sectioningUnit) continue; if (sectionNumber.size() < nextLevel) { do { out() << "<ul>"; sectionNumber.append("1"); } while (sectionNumber.size() < nextLevel); } else { while (sectionNumber.size() > nextLevel) { out() << "</ul>\n"; sectionNumber.removeLast(); } sectionNumber.last() = QString::number(sectionNumber.last().toInt() + 1); } int numAtoms; Text headingText = Text::sectionHeading(atom); if (sectionNumber.size() == 1 && columnSize > toc.size() / numColumns) { out() << "</ul></td>" << tdTag << "<ul>\n"; columnSize = 0; } out() << "<li>"; out() << "<a href=\"" << nodeName << "#" << Doc::canonicalTitle(headingText.toString()) << "\">"; generateAtomList(headingText.firstAtom(), node, marker, true, numAtoms); out() << "</a></li>\n"; ++columnSize; } while (!sectionNumber.isEmpty()) { out() << "</ul>\n"; sectionNumber.removeLast(); } if (numColumns > 1) out() << "</td></tr></table>\n"; inContents = false; inLink = false; } /*! Revised for the new doc format. Generates a table of contents begining at \a node. */ void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker, QList<Section>* sections) { QList<Atom*> toc; if (node->doc().hasTableOfContents()) toc = node->doc().tableOfContents(); if (toc.isEmpty() && !sections && (node->subType() != Node::Module)) return; QStringList sectionNumber; int detailsBase = 0; // disable nested links in table of contents inContents = true; inLink = true; out() << "<div class=\"toc\">\n"; out() << "<h3>Table of Contents</h3>\n"; sectionNumber.append("1"); out() << "<ul class=\"level" << sectionNumber.size() << "\">\n"; if (node->subType() == Node::Module) { if (moduleNamespaceMap.contains(node->name())) { out() << "<li><a href=\"#" << registerRef("namespaces") << "\">Namespaces</a></li>\n"; } if (moduleClassMap.contains(node->name())) { out() << "<li><a href=\"#" << registerRef("classes") << "\">Classes</a></li>\n"; } out() << "<li><a href=\"#" << registerRef("details") << "\">Detailed Description</a></li>\n"; for (int i = 0; i < toc.size(); ++i) { if (toc.at(i)->string().toInt() == 1) { detailsBase = 1; break; } } } else if (sections && (node->type() == Node::Class)) { QList<Section>::ConstIterator s = sections->begin(); while (s != sections->end()) { if (!s->members.isEmpty() || !s->reimpMembers.isEmpty()) { out() << "<li><a href=\"#" << registerRef((*s).pluralMember) << "\">" << (*s).name << "</a></li>\n"; } ++s; } out() << "<li><a href=\"#" << registerRef("details") << "\">Detailed Description</a></li>\n"; for (int i = 0; i < toc.size(); ++i) { if (toc.at(i)->string().toInt() == 1) { detailsBase = 1; break; } } } for (int i = 0; i < toc.size(); ++i) { Atom *atom = toc.at(i); int nextLevel = atom->string().toInt() + detailsBase; if (sectionNumber.size() < nextLevel) { do { sectionNumber.append("1"); out() << "<ul class=\"level" << sectionNumber.size() << "\">\n"; } while (sectionNumber.size() < nextLevel); } else { while (sectionNumber.size() > nextLevel) { out() << "</ul>\n"; sectionNumber.removeLast(); } sectionNumber.last() = QString::number(sectionNumber.last().toInt() + 1); } int numAtoms; Text headingText = Text::sectionHeading(atom); QString s = headingText.toString(); out() << "<li>"; out() << "<a href=\"" << "#" //<< registerRef(s) << Doc::canonicalTitle(s) << "\">"; generateAtomList(headingText.firstAtom(), node, marker, true, numAtoms); out() << "</a></li>\n"; } while (!sectionNumber.isEmpty()) { out() << "</ul>\n"; sectionNumber.removeLast(); } out() << "</div>\n"; inContents = false; inLink = false; } #if 0 void HtmlGenerator::generateNavigationBar(const NavigationBar& bar, const Node *node, CodeMarker *marker) { if (bar.prev.begin() != 0 || bar.current.begin() != 0 || bar.next.begin() != 0) { out() << "<p class=\"rightAlign\">"; if (bar.prev.begin() != 0) { #if 0 out() << "[<a href=\"" << section.previousBaseName() << ".html\">Prev: "; generateText(section.previousHeading(), node, marker); out() << "</a>]\n"; #endif } if (bar.current.begin() != 0) { out() << "[<a href=\"" << "home" << ".html\">Home</a>]\n"; } if (bar.next.begin() != 0) { out() << "[<a href=\"" << fileBase(node, bar.next) << ".html\">Next: "; generateText(Text::sectionHeading(bar.next.begin()), node, marker); out() << "</a>]\n"; } out() << "</p>\n"; } } #endif QString HtmlGenerator::generateListOfAllMemberFile(const InnerNode *inner, CodeMarker *marker) { QList<Section> sections; QList<Section>::ConstIterator s; sections = marker->sections(inner, CodeMarker::SeparateList, CodeMarker::Okay); if (sections.isEmpty()) return QString(); QString fileName = fileBase(inner) + "-members." + fileExtension(inner); beginSubPage(inner->location(), fileName); QString title = "List of All Members for " + inner->name(); generateHeader(title, inner, marker, false); generateTitle(title, Text(), SmallSubTitle, inner, marker); out() << "<p>This is the complete list of members for "; generateFullName(inner, 0, marker); out() << ", including inherited members.</p>\n"; Section section = sections.first(); generateSectionList(section, 0, marker, CodeMarker::SeparateList); generateFooter(); endSubPage(); return fileName; } QString HtmlGenerator::generateLowStatusMemberFile(const InnerNode *inner, CodeMarker *marker, CodeMarker::Status status) { QList<Section> sections = marker->sections(inner, CodeMarker::Summary, status); QMutableListIterator<Section> j(sections); while (j.hasNext()) { if (j.next().members.size() == 0) j.remove(); } if (sections.isEmpty()) return QString(); int i; QString title; QString fileName; if (status == CodeMarker::Compat) { title = "Qt 3 Support Members for " + inner->name(); fileName = fileBase(inner) + "-qt3." + fileExtension(inner); } else { title = "Obsolete Members for " + inner->name(); fileName = fileBase(inner) + "-obsolete." + fileExtension(inner); } beginSubPage(inner->location(), fileName); generateHeader(title, inner, marker, false); generateTitle(title, Text(), SmallSubTitle, inner, marker); if (status == CodeMarker::Compat) { out() << "<p><b>The following class members are part of the " "<a href=\"qt3support.html\">Qt 3 support layer</a>.</b> " "They are provided to help you port old code to Qt 4. We advise against " "using them in new code.</p>\n"; } else { out() << "<p><b>The following class members are obsolete.</b> " << "They are provided to keep old source code working. " << "We strongly advise against using them in new code.</p>\n"; } out() << "<p><ul><li><a href=\"" << linkForNode(inner, 0) << "\">" << protectEnc(inner->name()) << " class reference</a></li></ul></p>\n"; for (i = 0; i < sections.size(); ++i) { out() << "<h2>" << protectEnc(sections.at(i).name) << "</h2>\n"; generateSectionList(sections.at(i), inner, marker, CodeMarker::Summary); } sections = marker->sections(inner, CodeMarker::Detailed, status); for (i = 0; i < sections.size(); ++i) { out() << "<hr />\n"; out() << "<h2>" << protectEnc(sections.at(i).name) << "</h2>\n"; NodeList::ConstIterator m = sections.at(i).members.begin(); while (m != sections.at(i).members.end()) { if ((*m)->access() != Node::Private) generateDetailedMember(*m, inner, marker); ++m; } } generateFooter(); endSubPage(); return fileName; } void HtmlGenerator::generateClassHierarchy(const Node *relative, CodeMarker *marker, const QMap<QString,const Node*> &classMap) { if (classMap.isEmpty()) return; NodeMap topLevel; NodeMap::ConstIterator c = classMap.begin(); while (c != classMap.end()) { const ClassNode *classe = static_cast<const ClassNode *>(*c); if (classe->baseClasses().isEmpty()) topLevel.insert(classe->name(), classe); ++c; } QStack<NodeMap > stack; stack.push(topLevel); out() << "<ul>\n"; while (!stack.isEmpty()) { if (stack.top().isEmpty()) { stack.pop(); out() << "</ul>\n"; } else { const ClassNode *child = static_cast<const ClassNode *>(*stack.top().begin()); out() << "<li>"; generateFullName(child, relative, marker); out() << "</li>\n"; stack.top().erase(stack.top().begin()); NodeMap newTop; foreach (const RelatedClass &d, child->derivedClasses()) { if (d.access != Node::Private) newTop.insert(d.node->name(), d.node); } if (!newTop.isEmpty()) { stack.push(newTop); out() << "<ul>\n"; } } } } void HtmlGenerator::generateAnnotatedList(const Node *relative, CodeMarker *marker, const NodeMap &nodeMap) { out() << "<table class=\"annotated\">\n"; int row = 0; foreach (const QString &name, nodeMap.keys()) { const Node *node = nodeMap[name]; if (node->status() == Node::Obsolete) continue; if (++row % 2 == 1) out() << "<tr class=\"odd topAlign\">"; else out() << "<tr class=\"even topAlign\">"; out() << "<th>"; generateFullName(node, relative, marker); out() << "</th>"; if (!(node->type() == Node::Fake)) { Text brief = node->doc().trimmedBriefText(name); if (!brief.isEmpty()) { out() << "<td>"; generateText(brief, node, marker); out() << "</td>"; } } else { out() << "<td>"; out() << protectEnc(node->doc().briefText().toString()); out() << "</td>"; } out() << "</tr>\n"; } out() << "</table>\n"; } /*! This function finds the common prefix of the names of all the classes in \a classMap and then generates a compact list of the class names alphabetized on the part of the name not including the common prefix. You can tell the function to use \a comonPrefix as the common prefix, but normally you let it figure it out itself by looking at the name of the first and last classes in \a classMap. */ void HtmlGenerator::generateCompactList(const Node *relative, CodeMarker *marker, const NodeMap &classMap, QString commonPrefix) { const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_' const int NumColumns = 3; // number of columns in the result if (classMap.isEmpty()) return; /* If commonPrefix is not empty, then the caller knows what the common prefix is and has passed it in, so just use that one. */ int commonPrefixLen = commonPrefix.length(); if (commonPrefixLen == 0) { QString first; QString last; /* The caller didn't pass in a common prefix, so get the common prefix by looking at the class names of the first and last classes in the class map. Discard any namespace names and just use the bare class names. For Qt, the prefix is "Q". Note that the algorithm used here to derive the common prefix from the first and last classes in alphabetical order (QAccel and QXtWidget in Qt 2.1), fails if either class name does not begin with Q. */ NodeMap::const_iterator iter = classMap.begin(); while (iter != classMap.end()) { if (!iter.key().contains("::")) { first = iter.key(); break; } ++iter; } if (first.isEmpty()) first = classMap.begin().key(); iter = classMap.end(); while (iter != classMap.begin()) { --iter; if (!iter.key().contains("::")) { last = iter.key(); break; } } if (last.isEmpty()) last = classMap.begin().key(); if (classMap.size() > 1) { while (commonPrefixLen < first.length() + 1 && commonPrefixLen < last.length() + 1 && first[commonPrefixLen] == last[commonPrefixLen]) ++commonPrefixLen; } commonPrefix = first.left(commonPrefixLen); } /* Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z, underscore (_). QAccel will fall in paragraph 10 (A) and QXtWidget in paragraph 33 (X). This is the only place where we assume that NumParagraphs is 37. Each paragraph is a NodeMap. */ NodeMap paragraph[NumParagraphs+1]; QString paragraphName[NumParagraphs+1]; NodeMap::ConstIterator c = classMap.begin(); while (c != classMap.end()) { QStringList pieces = c.key().split("::"); QString key; int idx = commonPrefixLen; if (!pieces.last().startsWith(commonPrefix)) idx = 0; if (pieces.size() == 1) key = pieces.last().mid(idx).toLower(); else key = pieces.last().toLower(); int paragraphNo = NumParagraphs - 1; if (key[0].digitValue() != -1) { paragraphNo = key[0].digitValue(); } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) { paragraphNo = 10 + key[0].unicode() - 'a'; } paragraphName[paragraphNo] = key[0].toUpper(); paragraph[paragraphNo].insert(key, c.value()); ++c; } /* Each paragraph j has a size: paragraph[j].count(). In the discussion, we will assume paragraphs 0 to 5 will have sizes 3, 1, 4, 1, 5, 9. We now want to compute the paragraph offset. Paragraphs 0 to 6 start at offsets 0, 3, 4, 8, 9, 14, 23. */ int paragraphOffset[NumParagraphs + 1]; // 37 + 1 int i, j, k; paragraphOffset[0] = 0; for (j = 0; j < NumParagraphs; j++) // j = 0..36 paragraphOffset[j + 1] = paragraphOffset[j] + paragraph[j].count(); int firstOffset[NumColumns + 1]; // 4 + 1 int currentOffset[NumColumns]; // 4 int currentParagraphNo[NumColumns]; // 4 int currentOffsetInParagraph[NumColumns]; // 4 int numRows = (classMap.count() + NumColumns - 1) / NumColumns; int curParagNo = 0; for (i = 0; i < NumColumns; i++) { // i = 0..3 firstOffset[i] = qMin(i * numRows, classMap.size()); currentOffset[i] = firstOffset[i]; for (j = curParagNo; j < NumParagraphs; j++) { if (paragraphOffset[j] > firstOffset[i]) break; if (paragraphOffset[j] <= firstOffset[i]) curParagNo = j; } currentParagraphNo[i] = curParagNo; currentOffsetInParagraph[i] = firstOffset[i] - paragraphOffset[curParagNo]; } firstOffset[NumColumns] = classMap.count(); out() << "<table class=\"generic\">\n"; for (k = 0; k < numRows; k++) { out() << "<tr>\n"; for (i = 0; i < NumColumns; i++) { if (currentOffset[i] >= firstOffset[i + 1]) { // this column is finished out() << "<td>\n</td>\n"; } else { while ((currentParagraphNo[i] < NumParagraphs) && (currentOffsetInParagraph[i] == paragraph[currentParagraphNo[i]].count())) { ++currentParagraphNo[i]; currentOffsetInParagraph[i] = 0; } #if 0 if (currentParagraphNo[i] >= NumParagraphs) { qDebug() << "### Internal error ###" << __FILE__ << __LINE__ << currentParagraphNo[i] << NumParagraphs; currentParagraphNo[i] = NumParagraphs - 1; } #endif out() << "<td class=\"rightAlign\">"; if (currentOffsetInParagraph[i] == 0) { // start a new paragraph out() << "<b>" << paragraphName[currentParagraphNo[i]] << "&nbsp;</b>"; } out() << "</td>\n"; out() << "<td>"; if ((currentParagraphNo[i] < NumParagraphs) && !paragraphName[currentParagraphNo[i]].isEmpty()) { NodeMap::Iterator it; it = paragraph[currentParagraphNo[i]].begin(); for (j = 0; j < currentOffsetInParagraph[i]; j++) ++it; // Previously, we used generateFullName() for this, but we // require some special formatting. out() << "<a href=\"" << linkForNode(it.value(), relative) << "\">"; QStringList pieces; if (it.value()->subType() == Node::QmlClass) pieces << it.value()->name(); else pieces = fullName(it.value(), relative, marker).split("::"); out() << protectEnc(pieces.last()); out() << "</a>"; if (pieces.size() > 1) { out() << " ("; generateFullName(it.value()->parent(), relative, marker); out() << ")"; } } out() << "</td>\n"; currentOffset[i]++; currentOffsetInParagraph[i]++; } } out() << "</tr>\n"; } out() << "</table>\n"; } void HtmlGenerator::generateFunctionIndex(const Node *relative, CodeMarker *marker) { out() << "<p class=\"centerAlign functionIndex\"><b>"; for (int i = 0; i < 26; i++) { QChar ch('a' + i); out() << QString("<a href=\"#%1\">%2</a>&nbsp;").arg(ch).arg(ch.toUpper()); } out() << "</b></p>\n"; char nextLetter = 'a'; char currentLetter; #if 1 out() << "<ul>\n"; #endif QMap<QString, NodeMap >::ConstIterator f = funcIndex.begin(); while (f != funcIndex.end()) { #if 1 out() << "<li>"; #else out() << "<p>"; #endif out() << protectEnc(f.key()) << ":"; currentLetter = f.key()[0].unicode(); while (islower(currentLetter) && currentLetter >= nextLetter) { out() << QString("<a name=\"%1\"></a>").arg(nextLetter); nextLetter++; } NodeMap::ConstIterator s = (*f).begin(); while (s != (*f).end()) { out() << " "; generateFullName((*s)->parent(), relative, marker, *s); ++s; } #if 1 out() << "</li>"; #else out() << "</p>"; #endif out() << "\n"; ++f; } #if 1 out() << "</ul>\n"; #endif } void HtmlGenerator::generateLegaleseList(const Node *relative, CodeMarker *marker) { QMap<Text, const Node *>::ConstIterator it = legaleseTexts.begin(); while (it != legaleseTexts.end()) { Text text = it.key(); out() << "<hr />\n"; generateText(text, relative, marker); out() << "<ul>\n"; do { out() << "<li>"; generateFullName(it.value(), relative, marker); out() << "</li>\n"; ++it; } while (it != legaleseTexts.end() && it.key() == text); out() << "</ul>\n"; } } /*void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { QString marked = marker->markedUpSynopsis(node, relative, style); QRegExp templateTag("(<[^@>]*>)"); if (marked.indexOf(templateTag) != -1) { QString contents = protectEnc(marked.mid(templateTag.pos(1), templateTag.cap(1).length())); marked.replace(templateTag.pos(1), templateTag.cap(1).length(), contents); } marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), "<i>\\1<sub>\\2</sub></i>"); marked.replace("<@param>", "<i>"); marked.replace("</@param>", "</i>"); if (style == CodeMarker::Summary) marked.replace("@name>", "b>"); if (style == CodeMarker::SeparateList) { QRegExp extraRegExp("<@extra>.*</@extra>"); extraRegExp.setMinimal(true); marked.replace(extraRegExp, ""); } else { marked.replace("<@extra>", "&nbsp;&nbsp;<tt>"); marked.replace("</@extra>", "</tt>"); } if (style != CodeMarker::Detailed) { marked.replace("<@type>", ""); marked.replace("</@type>", ""); } out() << highlightedCode(marked, marker, relative); }*/ #ifdef QDOC_QML void HtmlGenerator::generateQmlItem(const Node *node, const Node *relative, CodeMarker *marker, bool summary) { QString marked = marker->markedUpQmlItem(node,summary); QRegExp templateTag("(<[^@>]*>)"); if (marked.indexOf(templateTag) != -1) { QString contents = protectEnc(marked.mid(templateTag.pos(1), templateTag.cap(1).length())); marked.replace(templateTag.pos(1), templateTag.cap(1).length(), contents); } marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), "<i>\\1<sub>\\2</sub></i>"); marked.replace("<@param>", "<i>"); marked.replace("</@param>", "</i>"); if (summary) marked.replace("@name>", "b>"); marked.replace("<@extra>", "&nbsp;&nbsp;<tt>"); marked.replace("</@extra>", "</tt>"); if (summary) { marked.replace("<@type>", ""); marked.replace("</@type>", ""); } out() << highlightedCode(marked, marker, relative); } #endif void HtmlGenerator::generateOverviewList(const Node *relative, CodeMarker * /* marker */) { QMap<const FakeNode *, QMap<QString, FakeNode *> > fakeNodeMap; QMap<QString, const FakeNode *> groupTitlesMap; QMap<QString, FakeNode *> uncategorizedNodeMap; QRegExp singleDigit("\\b([0-9])\\b"); const NodeList children = myTree->root()->childNodes(); foreach (Node *child, children) { if (child->type() == Node::Fake && child != relative) { FakeNode *fakeNode = static_cast<FakeNode *>(child); // Check whether the page is part of a group or is the group // definition page. QString group; bool isGroupPage = false; if (fakeNode->doc().metaCommandsUsed().contains("group")) { group = fakeNode->doc().metaCommandArgs("group")[0]; isGroupPage = true; } // there are too many examples; they would clutter the list if (fakeNode->subType() == Node::Example) continue; // not interested either in individual (Qt Designer etc.) manual chapters if (fakeNode->links().contains(Node::ContentsLink)) continue; // Discard external nodes. if (fakeNode->subType() == Node::ExternalPage) continue; QString sortKey = fakeNode->fullTitle().toLower(); if (sortKey.startsWith("the ")) sortKey.remove(0, 4); sortKey.replace(singleDigit, "0\\1"); if (!group.isEmpty()) { if (isGroupPage) { // If we encounter a group definition page, we add all // the pages in that group to the list for that group. foreach (Node *member, fakeNode->groupMembers()) { if (member->type() != Node::Fake) continue; FakeNode *page = static_cast<FakeNode *>(member); if (page) { QString sortKey = page->fullTitle().toLower(); if (sortKey.startsWith("the ")) sortKey.remove(0, 4); sortKey.replace(singleDigit, "0\\1"); fakeNodeMap[const_cast<const FakeNode *>(fakeNode)].insert(sortKey, page); groupTitlesMap[fakeNode->fullTitle()] = const_cast<const FakeNode *>(fakeNode); } } } else if (!isGroupPage) { // If we encounter a page that belongs to a group then // we add that page to the list for that group. const FakeNode *groupNode = static_cast<const FakeNode *>(myTree->root()->findNode(group, Node::Fake)); if (groupNode) fakeNodeMap[groupNode].insert(sortKey, fakeNode); //else // uncategorizedNodeMap.insert(sortKey, fakeNode); }// else // uncategorizedNodeMap.insert(sortKey, fakeNode); }// else // uncategorizedNodeMap.insert(sortKey, fakeNode); } } // We now list all the pages found that belong to groups. // If only certain pages were found for a group, but the definition page // for that group wasn't listed, the list of pages will be intentionally // incomplete. However, if the group definition page was listed, all the // pages in that group are listed for completeness. if (!fakeNodeMap.isEmpty()) { foreach (const QString &groupTitle, groupTitlesMap.keys()) { const FakeNode *groupNode = groupTitlesMap[groupTitle]; out() << QString("<h3><a href=\"%1\">%2</a></h3>\n").arg( linkForNode(groupNode, relative)).arg( protectEnc(groupNode->fullTitle())); if (fakeNodeMap[groupNode].count() == 0) continue; out() << "<ul>\n"; foreach (const FakeNode *fakeNode, fakeNodeMap[groupNode]) { QString title = fakeNode->fullTitle(); if (title.startsWith("The ")) title.remove(0, 4); out() << "<li><a href=\"" << linkForNode(fakeNode, relative) << "\">" << protectEnc(title) << "</a></li>\n"; } out() << "</ul>\n"; } } if (!uncategorizedNodeMap.isEmpty()) { out() << QString("<h3>Miscellaneous</h3>\n"); out() << "<ul>\n"; foreach (const FakeNode *fakeNode, uncategorizedNodeMap) { QString title = fakeNode->fullTitle(); if (title.startsWith("The ")) title.remove(0, 4); out() << "<li><a href=\"" << linkForNode(fakeNode, relative) << "\">" << protectEnc(title) << "</a></li>\n"; } out() << "</ul>\n"; } } #ifdef QDOC_NAME_ALIGNMENT void HtmlGenerator::generateSection(const NodeList& nl, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { bool name_alignment = true; if (!nl.isEmpty()) { bool twoColumn = false; if (style == CodeMarker::SeparateList) { name_alignment = false; twoColumn = (nl.count() >= 16); } else if (nl.first()->type() == Node::Property) { twoColumn = (nl.count() >= 5); name_alignment = false; } if (name_alignment) { out() << "<table class=\"alignedsummary\">\n"; } else { if (twoColumn) out() << "<table class=\"propsummary\">\n" << "<tr><td class=\"topAlign\">"; out() << "<ul>\n"; } int i = 0; NodeList::ConstIterator m = nl.begin(); while (m != nl.end()) { if ((*m)->access() == Node::Private) { ++m; continue; } if (name_alignment) { out() << "<tr><td class=\"memItemLeft rightAlign topAlign\"> "; } else { if (twoColumn && i == (int) (nl.count() + 1) / 2) out() << "</ul></td><td class=\"topAlign\"><ul>\n"; out() << "<li><div class=\"fn\">"; } generateSynopsis(*m, relative, marker, style, name_alignment); if (name_alignment) out() << "</td></tr>\n"; else out() << "</div></li>\n"; i++; ++m; } if (name_alignment) out() << "</table>\n"; else { out() << "</ul>\n"; if (twoColumn) out() << "</td></tr>\n</table>\n"; } } } void HtmlGenerator::generateSectionList(const Section& section, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { bool name_alignment = true; if (!section.members.isEmpty()) { bool twoColumn = false; if (style == CodeMarker::SeparateList) { name_alignment = false; twoColumn = (section.members.count() >= 16); } else if (section.members.first()->type() == Node::Property) { twoColumn = (section.members.count() >= 5); name_alignment = false; } if (name_alignment) { out() << "<table class=\"alignedsummary\">\n"; } else { if (twoColumn) out() << "<table class=\"propsummary\">\n" << "<tr><td class=\"topAlign\">"; out() << "<ul>\n"; } int i = 0; NodeList::ConstIterator m = section.members.begin(); while (m != section.members.end()) { if ((*m)->access() == Node::Private) { ++m; continue; } if (name_alignment) { out() << "<tr><td class=\"memItemLeft topAlign rightAlign\"> "; } else { if (twoColumn && i == (int) (section.members.count() + 1) / 2) out() << "</ul></td><td class=\"topAlign\"><ul>\n"; out() << "<li><div class=\"fn\">"; } generateSynopsis(*m, relative, marker, style, name_alignment); if (name_alignment) out() << "</td></tr>\n"; else out() << "</div></li>\n"; i++; ++m; } if (name_alignment) out() << "</table>\n"; else { out() << "</ul>\n"; if (twoColumn) out() << "</td></tr>\n</table>\n"; } } if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { out() << "<ul>\n"; generateSectionInheritedList(section, relative, marker, name_alignment); out() << "</ul>\n"; } } void HtmlGenerator::generateSectionInheritedList(const Section& section, const Node *relative, CodeMarker *marker, bool nameAlignment) { QList<QPair<ClassNode *, int> >::ConstIterator p = section.inherited.begin(); while (p != section.inherited.end()) { if (nameAlignment) out() << "<li><div bar=\"2\" class=\"fn\"></div>"; else out() << "<li><div class=\"fn\"></div>"; out() << (*p).second << " "; if ((*p).second == 1) { out() << section.singularMember; } else { out() << section.pluralMember; } out() << " inherited from <a href=\"" << fileName((*p).first) << "#" << HtmlGenerator::cleanRef(section.name.toLower()) << "\">" << protectEnc(marker->plainFullName((*p).first, relative)) << "</a></li>\n"; ++p; } } void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style, bool nameAlignment) { QString marked = marker->markedUpSynopsis(node, relative, style); QRegExp templateTag("(<[^@>]*>)"); if (marked.indexOf(templateTag) != -1) { QString contents = protectEnc(marked.mid(templateTag.pos(1), templateTag.cap(1).length())); marked.replace(templateTag.pos(1), templateTag.cap(1).length(), contents); } marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), "<i>\\1<sub>\\2</sub></i>"); marked.replace("<@param>", "<i>"); marked.replace("</@param>", "</i>"); if (style == CodeMarker::Summary) { marked.replace("<@name>", ""); // was "<b>" marked.replace("</@name>", ""); // was "</b>" } if (style == CodeMarker::SeparateList) { QRegExp extraRegExp("<@extra>.*</@extra>"); extraRegExp.setMinimal(true); marked.replace(extraRegExp, ""); } else { marked.replace("<@extra>", "&nbsp;&nbsp;<tt>"); marked.replace("</@extra>", "</tt>"); } if (style != CodeMarker::Detailed) { marked.replace("<@type>", ""); marked.replace("</@type>", ""); } out() << highlightedCode(marked, marker, relative, style, nameAlignment); } QString HtmlGenerator::highlightedCode(const QString& markedCode, CodeMarker *marker, const Node *relative, CodeMarker::SynopsisStyle , bool nameAlignment) { QString src = markedCode; QString html; QStringRef arg; QStringRef par1; const QChar charLangle = '<'; const QChar charAt = '@'; // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)" static const QString linkTag("link"); bool done = false; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1).unicode() == '@') { if (nameAlignment && !done) {// && (i != 0)) Why was this here? html += "</td><td class=\"memItemRight bottomAlign\">"; done = true; } i += 2; if (parseArg(src, linkTag, &i, n, &arg, &par1)) { html += "<b>"; QString link = linkForNode( CodeMarker::nodeForString(par1.toString()), relative); addLink(link, arg, &html); html += "</b>"; } else { html += charLangle; html += charAt; } } else { html += src.at(i++); } } if (slow) { // is this block ever used at all? // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)" src = html; html = QString(); static const QString funcTag("func"); for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; if (parseArg(src, funcTag, &i, n, &arg, &par1)) { QString link = linkForNode( marker->resolveTarget(par1.toString(), myTree, relative), relative); addLink(link, arg, &html); par1 = QStringRef(); } else { html += charLangle; html += charAt; } } else { html += src.at(i++); } } } // replace all "(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)(</@\\2>)" tags src = html; html = QString(); static const QString typeTags[] = { "type", "headerfile", "func" }; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; bool handled = false; for (int k = 0; k != 3; ++k) { if (parseArg(src, typeTags[k], &i, n, &arg, &par1)) { par1 = QStringRef(); QString link = linkForNode( marker->resolveTarget(arg.toString(), myTree, relative), relative); addLink(link, arg, &html); handled = true; break; } } if (!handled) { html += charLangle; html += charAt; } } else { html += src.at(i++); } } // replace all // "<@comment>" -> "<span class=\"comment\">"; // "<@preprocessor>" -> "<span class=\"preprocessor\">"; // "<@string>" -> "<span class=\"string\">"; // "<@char>" -> "<span class=\"char\">"; // "</@(?:comment|preprocessor|string|char)>" -> "</span>" src = html; html = QString(); static const QString spanTags[] = { "<@comment>", "<span class=\"comment\">", "<@preprocessor>", "<span class=\"preprocessor\">", "<@string>", "<span class=\"string\">", "<@char>", "<span class=\"char\">", "</@comment>", "</span>", "</@preprocessor>","</span>", "</@string>", "</span>", "</@char>", "</span>" // "<@char>", "<font color=blue>", // "</@char>", "</font>", // "<@func>", "<font color=green>", // "</@func>", "</font>", // "<@id>", "<i>", // "</@id>", "</i>", // "<@keyword>", "<b>", // "</@keyword>", "</b>", // "<@number>", "<font color=yellow>", // "</@number>", "</font>", // "<@op>", "<b>", // "</@op>", "</b>", // "<@param>", "<i>", // "</@param>", "</i>", // "<@string>", "<font color=green>", // "</@string>", "</font>", }; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle) { bool handled = false; for (int k = 0; k != 8; ++k) { const QString & tag = spanTags[2 * k]; if (tag == QStringRef(&src, i, tag.length())) { html += spanTags[2 * k + 1]; i += tag.length(); handled = true; break; } } if (!handled) { ++i; if (src.at(i) == charAt || (src.at(i) == QLatin1Char('/') && src.at(i + 1) == charAt)) { // drop 'our' unknown tags (the ones still containing '@') while (i < n && src.at(i) != QLatin1Char('>')) ++i; ++i; } else { // retain all others html += charLangle; } } } else { html += src.at(i); ++i; } } return html; } #else void HtmlGenerator::generateSectionList(const Section& section, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { if (!section.members.isEmpty()) { bool twoColumn = false; if (style == CodeMarker::SeparateList) { twoColumn = (section.members.count() >= 16); } else if (section.members.first()->type() == Node::Property) { twoColumn = (section.members.count() >= 5); } if (twoColumn) out() << "<table class=\"generic\">\n" << "<tr><td class=\"topAlign\">"; out() << "<ul>\n"; int i = 0; NodeList::ConstIterator m = section.members.begin(); while (m != section.members.end()) { if ((*m)->access() == Node::Private) { ++m; continue; } if (twoColumn && i == (int) (section.members.count() + 1) / 2) out() << "</ul></td><td class=\"topAlign\"><ul>\n"; out() << "<li><div class=\"fn\"></div>"; if (style == CodeMarker::Accessors) out() << "<b>"; generateSynopsis(*m, relative, marker, style); if (style == CodeMarker::Accessors) out() << "</b>"; out() << "</li>\n"; i++; ++m; } out() << "</ul>\n"; if (twoColumn) out() << "</td></tr>\n</table>\n"; } if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { out() << "<ul>\n"; generateSectionInheritedList(section, relative, marker); out() << "</ul>\n"; } } void HtmlGenerator::generateSectionInheritedList(const Section& section, const Node *relative, CodeMarker *marker) { QList<QPair<ClassNode *, int> >::ConstIterator p = section.inherited.begin(); while (p != section.inherited.end()) { out() << "<li><div bar=\"2\" class=\"fn\"></div>"; out() << (*p).second << " "; if ((*p).second == 1) { out() << section.singularMember; } else { out() << section.pluralMember; } out() << " inherited from <a href=\"" << fileName((*p).first) << "#" << HtmlGenerator::cleanRef(section.name.toLower()) << "\">" << protectEnc(marker->plainFullName((*p).first, relative)) << "</a></li>\n"; ++p; } } void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { QString marked = marker->markedUpSynopsis(node, relative, style); QRegExp templateTag("(<[^@>]*>)"); if (marked.indexOf(templateTag) != -1) { QString contents = protectEnc(marked.mid(templateTag.pos(1), templateTag.cap(1).length())); marked.replace(templateTag.pos(1), templateTag.cap(1).length(), contents); } marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), "<i>\\1<sub>\\2</sub></i>"); marked.replace("<@param>", "<i>"); marked.replace("</@param>", "</i>"); if (style == CodeMarker::Summary) marked.replace("@name>", "b>"); if (style == CodeMarker::SeparateList) { QRegExp extraRegExp("<@extra>.*</@extra>"); extraRegExp.setMinimal(true); marked.replace(extraRegExp, ""); } else { marked.replace("<@extra>", "&nbsp;&nbsp;<tt>"); marked.replace("</@extra>", "</tt>"); } if (style != CodeMarker::Detailed) { marked.replace("<@type>", ""); marked.replace("</@type>", ""); } out() << highlightedCode(marked, marker, relative); } QString HtmlGenerator::highlightedCode(const QString& markedCode, CodeMarker *marker, const Node *relative) { QString src = markedCode; QString html; QStringRef arg; QStringRef par1; const QChar charLangle = '<'; const QChar charAt = '@'; // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)" static const QString linkTag("link"); for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; if (parseArg(src, linkTag, &i, n, &arg, &par1)) { const Node* node = CodeMarker::nodeForString(par1.toString()); QString link = linkForNode(node, relative); addLink(link, arg, &html); } else { html += charLangle; html += charAt; } } else { html += src.at(i++); } } if (slow) { // is this block ever used at all? // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)" src = html; html = QString(); static const QString funcTag("func"); for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; if (parseArg(src, funcTag, &i, n, &arg, &par1)) { QString link = linkForNode( marker->resolveTarget(par1.toString(), myTree, relative), relative); addLink(link, arg, &html); par1 = QStringRef(); } else { html += charLangle; html += charAt; } } else { html += src.at(i++); } } } // replace all "(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)(</@\\2>)" tags src = html; html = QString(); static const QString typeTags[] = { "type", "headerfile", "func" }; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; bool handled = false; for (int k = 0; k != 3; ++k) { if (parseArg(src, typeTags[k], &i, n, &arg, &par1)) { par1 = QStringRef(); QString link = linkForNode( marker->resolveTarget(arg.toString(), myTree, relative), relative); addLink(link, arg, &html); handled = true; break; } } if (!handled) { html += charLangle; html += charAt; } } else { html += src.at(i++); } } // replace all // "<@comment>" -> "<span class=\"comment\">"; // "<@preprocessor>" -> "<span class=\"preprocessor\">"; // "<@string>" -> "<span class=\"string\">"; // "<@char>" -> "<span class=\"char\">"; // "</@(?:comment|preprocessor|string|char)>" -> "</span>" src = html; html = QString(); static const QString spanTags[] = { "<@comment>", "<span class=\"comment\">", "<@preprocessor>", "<span class=\"preprocessor\">", "<@string>", "<span class=\"string\">", "<@char>", "<span class=\"char\">", "</@comment>", "</span>", "</@preprocessor>","</span>", "</@string>", "</span>", "</@char>", "</span>" // "<@char>", "<font color=blue>", // "</@char>", "</font>", // "<@func>", "<font color=green>", // "</@func>", "</font>", // "<@id>", "<i>", // "</@id>", "</i>", // "<@keyword>", "<b>", // "</@keyword>", "</b>", // "<@number>", "<font color=yellow>", // "</@number>", "</font>", // "<@op>", "<b>", // "</@op>", "</b>", // "<@param>", "<i>", // "</@param>", "</i>", // "<@string>", "<font color=green>", // "</@string>", "</font>", }; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle) { bool handled = false; for (int k = 0; k != 8; ++k) { const QString & tag = spanTags[2 * k]; if (tag == QStringRef(&src, i, tag.length())) { html += spanTags[2 * k + 1]; i += tag.length(); handled = true; break; } } if (!handled) { ++i; if (src.at(i) == charAt || (src.at(i) == QLatin1Char('/') && src.at(i + 1) == charAt)) { // drop 'our' unknown tags (the ones still containing '@') while (i < n && src.at(i) != QLatin1Char('>')) ++i; ++i; } else { // retain all others html += charLangle; } } } else { html += src.at(i); ++i; } } return html; } #endif void HtmlGenerator::generateLink(const Atom* atom, const Node* /* relative */, CodeMarker* marker) { static QRegExp camelCase("[A-Z][A-Z][a-z]|[a-z][A-Z0-9]|_"); if (funcLeftParen.indexIn(atom->string()) != -1 && marker->recognizeLanguage("Cpp")) { // hack for C++: move () outside of link int k = funcLeftParen.pos(1); out() << protectEnc(atom->string().left(k)); if (link.isEmpty()) { if (showBrokenLinks) out() << "</i>"; } else { out() << "</a>"; } inLink = false; out() << protectEnc(atom->string().mid(k)); } else if (marker->recognizeLanguage("Java")) { // hack for Java: remove () and use <tt> when appropriate bool func = atom->string().endsWith("()"); bool tt = (func || atom->string().contains(camelCase)); if (tt) out() << "<tt>"; if (func) { out() << protectEnc(atom->string().left(atom->string().length() - 2)); } else { out() << protectEnc(atom->string()); } out() << "</tt>"; } else { out() << protectEnc(atom->string()); } } QString HtmlGenerator::cleanRef(const QString& ref) { QString clean; if (ref.isEmpty()) return clean; clean.reserve(ref.size() + 20); const QChar c = ref[0]; const uint u = c.unicode(); if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9')) { clean += c; } else if (u == '~') { clean += "dtor."; } else if (u == '_') { clean += "underscore."; } else { clean += "A"; } for (int i = 1; i < (int) ref.length(); i++) { const QChar c = ref[i]; const uint u = c.unicode(); if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '-' || u == '_' || u == ':' || u == '.') { clean += c; } else if (c.isSpace()) { clean += "-"; } else if (u == '!') { clean += "-not"; } else if (u == '&') { clean += "-and"; } else if (u == '<') { clean += "-lt"; } else if (u == '=') { clean += "-eq"; } else if (u == '>') { clean += "-gt"; } else if (u == '#') { clean += "#"; } else { clean += "-"; clean += QString::number((int)u, 16); } } return clean; } QString HtmlGenerator::registerRef(const QString& ref) { QString clean = HtmlGenerator::cleanRef(ref); for (;;) { QString& prevRef = refMap[clean.toLower()]; if (prevRef.isEmpty()) { prevRef = ref; break; } else if (prevRef == ref) { break; } clean += "x"; } return clean; } QString HtmlGenerator::protectEnc(const QString &string) { return protect(string, outputEncoding); } QString HtmlGenerator::protect(const QString &string, const QString &outputEncoding) { #define APPEND(x) \ if (html.isEmpty()) { \ html = string; \ html.truncate(i); \ } \ html += (x); QString html; int n = string.length(); for (int i = 0; i < n; ++i) { QChar ch = string.at(i); if (ch == QLatin1Char('&')) { APPEND("&amp;"); } else if (ch == QLatin1Char('<')) { APPEND("&lt;"); } else if (ch == QLatin1Char('>')) { APPEND("&gt;"); } else if (ch == QLatin1Char('"')) { APPEND("&quot;"); } else if ((outputEncoding == "ISO-8859-1" && ch.unicode() > 0x007F) || (ch == QLatin1Char('*') && i + 1 < n && string.at(i) == QLatin1Char('/')) || (ch == QLatin1Char('.') && i > 2 && string.at(i - 2) == QLatin1Char('.'))) { // we escape '*/' and the last dot in 'e.g.' and 'i.e.' for the Javadoc generator APPEND("&#x"); html += QString::number(ch.unicode(), 16); html += QLatin1Char(';'); } else { if (!html.isEmpty()) html += ch; } } if (!html.isEmpty()) return html; return string; #undef APPEND } QString HtmlGenerator::fileBase(const Node *node) { QString result; result = PageGenerator::fileBase(node); if (!node->isInnerNode()) { switch (node->status()) { case Node::Compat: result += "-qt3"; break; case Node::Obsolete: result += "-obsolete"; break; default: ; } } return result; } #if 0 QString HtmlGenerator::fileBase(const Node *node, const SectionIterator& section) { QStringList::ConstIterator s = section.sectionNumber().end(); QStringList::ConstIterator b = section.baseNameStack().end(); QString suffix; QString base = fileBase(node); while (s != section.sectionNumber().begin()) { --s; --b; if (!(*b).isEmpty()) { base = *b; break; } suffix.prepend("-" + *s); } return base + suffix; } #endif QString HtmlGenerator::fileName(const Node *node) { if (node->type() == Node::Fake) { if (static_cast<const FakeNode *>(node)->subType() == Node::ExternalPage) return node->name(); if (static_cast<const FakeNode *>(node)->subType() == Node::Image) return node->name(); } return PageGenerator::fileName(node); } QString HtmlGenerator::refForNode(const Node *node) { const FunctionNode *func; const TypedefNode *typedeffe; QString ref; switch (node->type()) { case Node::Namespace: case Node::Class: default: break; case Node::Enum: ref = node->name() + "-enum"; break; case Node::Typedef: typedeffe = static_cast<const TypedefNode *>(node); if (typedeffe->associatedEnum()) { return refForNode(typedeffe->associatedEnum()); } else { ref = node->name() + "-typedef"; } break; case Node::Function: func = static_cast<const FunctionNode *>(node); if (func->associatedProperty()) { return refForNode(func->associatedProperty()); } else { ref = func->name(); if (func->overloadNumber() != 1) ref += "-" + QString::number(func->overloadNumber()); } break; #ifdef QDOC_QML case Node::Fake: if (node->subType() != Node::QmlPropertyGroup) break; case Node::QmlProperty: #endif case Node::Property: ref = node->name() + "-prop"; break; #ifdef QDOC_QML case Node::QmlSignal: ref = node->name() + "-signal"; break; case Node::QmlMethod: ref = node->name() + "-method"; break; #endif case Node::Variable: ref = node->name() + "-var"; break; case Node::Target: return protectEnc(node->name()); } return registerRef(ref); } QString HtmlGenerator::linkForNode(const Node *node, const Node *relative) { QString link; QString fn; QString ref; if (node == 0 || node == relative) return QString(); if (!node->url().isEmpty()) return node->url(); if (fileBase(node).isEmpty()) return QString(); if (node->access() == Node::Private) return QString(); fn = fileName(node); /* if (!node->url().isEmpty()) return fn;*/ #if 0 // ### reintroduce this test, without breaking .dcf files if (fn != outFileName()) #endif link += fn; if (!node->isInnerNode() || node->subType() == Node::QmlPropertyGroup) { ref = refForNode(node); if (relative && fn == fileName(relative) && ref == refForNode(relative)) return QString(); link += "#"; link += ref; } return link; } QString HtmlGenerator::refForAtom(Atom *atom, const Node * /* node */) { if (atom->type() == Atom::SectionLeft) { return Doc::canonicalTitle(Text::sectionHeading(atom).toString()); } else if (atom->type() == Atom::Target) { return Doc::canonicalTitle(atom->string()); } else { return QString(); } } void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative, CodeMarker *marker, const Node *actualNode) { if (actualNode == 0) actualNode = apparentNode; out() << "<a href=\"" << linkForNode(actualNode, relative); if (true || relative == 0 || relative->status() != actualNode->status()) { switch (actualNode->status()) { case Node::Obsolete: out() << "\" class=\"obsolete"; break; case Node::Compat: out() << "\" class=\"compat"; break; default: ; } } out() << "\">"; out() << protectEnc(fullName(apparentNode, relative, marker)); out() << "</a>"; } void HtmlGenerator::generateDetailedMember(const Node *node, const InnerNode *relative, CodeMarker *marker) { const EnumNode *enume; generateMacRef(node, marker); if (node->type() == Node::Enum && (enume = static_cast<const EnumNode *>(node))->flagsType()) { generateMacRef(enume->flagsType(), marker); out() << "<h3 class=\"flags\">"; out() << "<a name=\"" + refForNode(node) + "\"></a>"; generateSynopsis(enume, relative, marker, CodeMarker::Detailed); out() << "<br />"; generateSynopsis(enume->flagsType(), relative, marker, CodeMarker::Detailed); out() << "</h3>\n"; } else { out() << "<h3 class=\"fn\">"; out() << "<a name=\"" + refForNode(node) + "\"></a>"; generateSynopsis(node, relative, marker, CodeMarker::Detailed); out() << "</h3>\n"; } generateStatus(node, marker); generateBody(node, marker); generateThreadSafeness(node, marker); generateSince(node, marker); if (node->type() == Node::Property) { const PropertyNode *property = static_cast<const PropertyNode *>(node); Section section; section.members += property->getters(); section.members += property->setters(); section.members += property->resetters(); if (!section.members.isEmpty()) { out() << "<p><b>Access functions:</b></p>\n"; generateSectionList(section, node, marker, CodeMarker::Accessors); } Section notifiers; notifiers.members += property->notifiers(); if (!notifiers.members.isEmpty()) { out() << "<p><b>Notifier signal:</b></p>\n"; //out() << "<p>This signal is emitted when the property value is changed.</p>\n"; generateSectionList(notifiers, node, marker, CodeMarker::Accessors); } } else if (node->type() == Node::Enum) { const EnumNode *enume = static_cast<const EnumNode *>(node); if (enume->flagsType()) { out() << "<p>The " << protectEnc(enume->flagsType()->name()) << " type is a typedef for " << "<a href=\"qflags.html\">QFlags</a>&lt;" << protectEnc(enume->name()) << "&gt;. It stores an OR combination of " << protectEnc(enume->name()) << " values.</p>\n"; } } generateAlsoList(node, marker); } void HtmlGenerator::findAllClasses(const InnerNode *node) { NodeList::const_iterator c = node->childNodes().constBegin(); while (c != node->childNodes().constEnd()) { if ((*c)->access() != Node::Private && (*c)->url().isEmpty()) { if ((*c)->type() == Node::Class && !(*c)->doc().isEmpty()) { QString className = (*c)->name(); if ((*c)->parent() && (*c)->parent()->type() == Node::Namespace && !(*c)->parent()->name().isEmpty()) className = (*c)->parent()->name()+"::"+className; if (!(static_cast<const ClassNode *>(*c))->hideFromMainList()) { if ((*c)->status() == Node::Compat) { compatClasses.insert(className, *c); } else if ((*c)->status() == Node::Obsolete) { obsoleteClasses.insert(className, *c); } else { nonCompatClasses.insert(className, *c); if ((*c)->status() == Node::Main) mainClasses.insert(className, *c); } } QString moduleName = (*c)->moduleName(); if (moduleName == "Qt3SupportLight") { moduleClassMap[moduleName].insert((*c)->name(), *c); moduleName = "Qt3Support"; } if (!moduleName.isEmpty()) moduleClassMap[moduleName].insert((*c)->name(), *c); QString serviceName = (static_cast<const ClassNode *>(*c))->serviceName(); if (!serviceName.isEmpty()) serviceClasses.insert(serviceName, *c); } else if ((*c)->isInnerNode()) { findAllClasses(static_cast<InnerNode *>(*c)); } } ++c; } } /*! For generating the "New Classes... in 4.6" section on the What's New in 4.6" page. */ void HtmlGenerator::findAllSince(const InnerNode *node) { NodeList::const_iterator child = node->childNodes().constBegin(); while (child != node->childNodes().constEnd()) { QString sinceVersion = (*child)->since(); if (((*child)->access() != Node::Private) && !sinceVersion.isEmpty()) { NewSinceMaps::iterator nsmap = newSinceMaps.find(sinceVersion); if (nsmap == newSinceMaps.end()) nsmap = newSinceMaps.insert(sinceVersion,NodeMultiMap()); NewClassMaps::iterator ncmap = newClassMaps.find(sinceVersion); if (ncmap == newClassMaps.end()) ncmap = newClassMaps.insert(sinceVersion,NodeMap()); NewClassMaps::iterator nqcmap = newQmlClassMaps.find(sinceVersion); if (nqcmap == newQmlClassMaps.end()) nqcmap = newQmlClassMaps.insert(sinceVersion,NodeMap()); if ((*child)->type() == Node::Function) { FunctionNode *func = static_cast<FunctionNode *>(*child); if ((func->status() > Node::Obsolete) && (func->metaness() != FunctionNode::Ctor) && (func->metaness() != FunctionNode::Dtor)) { nsmap.value().insert(func->name(),(*child)); } } else if ((*child)->url().isEmpty()) { if ((*child)->type() == Node::Class && !(*child)->doc().isEmpty()) { QString className = (*child)->name(); if ((*child)->parent() && (*child)->parent()->type() == Node::Namespace && !(*child)->parent()->name().isEmpty()) className = (*child)->parent()->name()+"::"+className; nsmap.value().insert(className,(*child)); ncmap.value().insert(className,(*child)); } else if ((*child)->subType() == Node::QmlClass) { QString className = (*child)->name(); if ((*child)->parent() && (*child)->parent()->type() == Node::Namespace && !(*child)->parent()->name().isEmpty()) className = (*child)->parent()->name()+"::"+className; nsmap.value().insert(className,(*child)); nqcmap.value().insert(className,(*child)); } } else { QString name = (*child)->name(); if ((*child)->parent() && (*child)->parent()->type() == Node::Namespace && !(*child)->parent()->name().isEmpty()) name = (*child)->parent()->name()+"::"+name; nsmap.value().insert(name,(*child)); } if ((*child)->isInnerNode()) { findAllSince(static_cast<InnerNode *>(*child)); } } ++child; } } #if 0 const QRegExp versionSeparator("[\\-\\.]"); const int minorIndex = version.indexOf(versionSeparator); const int patchIndex = version.indexOf(versionSeparator, minorIndex+1); version = version.left(patchIndex); #endif void HtmlGenerator::findAllFunctions(const InnerNode *node) { NodeList::ConstIterator c = node->childNodes().begin(); while (c != node->childNodes().end()) { if ((*c)->access() != Node::Private) { if ((*c)->isInnerNode() && (*c)->url().isEmpty()) { findAllFunctions(static_cast<const InnerNode *>(*c)); } else if ((*c)->type() == Node::Function) { const FunctionNode *func = static_cast<const FunctionNode *>(*c); if ((func->status() > Node::Obsolete) && (func->metaness() != FunctionNode::Ctor) && (func->metaness() != FunctionNode::Dtor)) { funcIndex[(*c)->name()].insert(myTree->fullDocumentName((*c)->parent()), *c); } } } ++c; } } void HtmlGenerator::findAllLegaleseTexts(const InnerNode *node) { NodeList::ConstIterator c = node->childNodes().begin(); while (c != node->childNodes().end()) { if ((*c)->access() != Node::Private) { if (!(*c)->doc().legaleseText().isEmpty()) legaleseTexts.insertMulti((*c)->doc().legaleseText(), *c); if ((*c)->isInnerNode()) findAllLegaleseTexts(static_cast<const InnerNode *>(*c)); } ++c; } } void HtmlGenerator::findAllNamespaces(const InnerNode *node) { NodeList::ConstIterator c = node->childNodes().begin(); while (c != node->childNodes().end()) { if ((*c)->access() != Node::Private) { if ((*c)->isInnerNode() && (*c)->url().isEmpty()) { findAllNamespaces(static_cast<const InnerNode *>(*c)); if ((*c)->type() == Node::Namespace) { const NamespaceNode *nspace = static_cast<const NamespaceNode *>(*c); // Ensure that the namespace's name is not empty (the root // namespace has no name). if (!nspace->name().isEmpty()) { namespaceIndex.insert(nspace->name(), *c); QString moduleName = (*c)->moduleName(); if (moduleName == "Qt3SupportLight") { moduleNamespaceMap[moduleName].insert((*c)->name(), *c); moduleName = "Qt3Support"; } if (!moduleName.isEmpty()) moduleNamespaceMap[moduleName].insert((*c)->name(), *c); } } } } ++c; } } #ifdef ZZZ_QDOC_QML /*! This function finds all the qml element nodes and stores them in a map for later use. */ void HtmlGenerator::findAllQmlClasses(const InnerNode *node) { NodeList::const_iterator c = node->childNodes().constBegin(); while (c != node->childNodes().constEnd()) { if ((*c)->type() == Node::Fake) { const FakeNode* fakeNode = static_cast<const FakeNode *>(*c); if (fakeNode->subType() == Node::QmlClass) { const QmlClassNode* qmlNode = static_cast<const QmlClassNode*>(fakeNode); const Node* n = qmlNode->classNode(); } qmlClasses.insert(fakeNode->name(),*c); } ++c; } } #endif int HtmlGenerator::hOffset(const Node *node) { switch (node->type()) { case Node::Namespace: case Node::Class: return 2; case Node::Fake: if (node->doc().briefText().isEmpty()) return 1; else return 2; case Node::Enum: case Node::Typedef: case Node::Function: case Node::Property: default: return 3; } } bool HtmlGenerator::isThreeColumnEnumValueTable(const Atom *atom) { while (atom != 0 && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) { if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight)) return true; atom = atom->next(); } return false; } const Node *HtmlGenerator::findNodeForTarget(const QString &target, const Node *relative, CodeMarker *marker, const Atom *atom) { const Node *node = 0; if (target.isEmpty()) { node = relative; } else if (target.endsWith(".html")) { node = myTree->root()->findNode(target, Node::Fake); } else if (marker) { node = marker->resolveTarget(target, myTree, relative); if (!node) node = myTree->findFakeNodeByTitle(target); if (!node && atom) { node = myTree->findUnambiguousTarget(target, *const_cast<Atom**>(&atom)); } } if (!node) relative->doc().location().warning(tr("Cannot link to '%1'").arg(target)); return node; } const QPair<QString,QString> HtmlGenerator::anchorForNode(const Node *node) { QPair<QString,QString> anchorPair; anchorPair.first = PageGenerator::fileName(node); if (node->type() == Node::Fake) { const FakeNode *fakeNode = static_cast<const FakeNode*>(node); anchorPair.second = fakeNode->title(); } return anchorPair; } QString HtmlGenerator::getLink(const Atom *atom, const Node *relative, CodeMarker *marker, const Node** node) { QString link; *node = 0; inObsoleteLink = false; if (atom->string().contains(":") && (atom->string().startsWith("file:") || atom->string().startsWith("http:") || atom->string().startsWith("https:") || atom->string().startsWith("ftp:") || atom->string().startsWith("mailto:"))) { link = atom->string(); } else { QStringList path; if (atom->string().contains('#')) { path = atom->string().split('#'); } else { path.append(atom->string()); } Atom *targetAtom = 0; QString first = path.first().trimmed(); if (first.isEmpty()) { *node = relative; } else if (first.endsWith(".html")) { *node = myTree->root()->findNode(first, Node::Fake); } else { *node = marker->resolveTarget(first, myTree, relative); if (!*node) { *node = myTree->findFakeNodeByTitle(first); } if (!*node) { *node = myTree->findUnambiguousTarget(first, targetAtom); } } if (*node) { if (!(*node)->url().isEmpty()) return (*node)->url(); else path.removeFirst(); } else { *node = relative; } if (*node) { if ((*node)->status() == Node::Obsolete) { if (relative) { if (relative->parent() != *node) { if (relative->status() != Node::Obsolete) { bool porting = false; if (relative->type() == Node::Fake) { const FakeNode* fake = static_cast<const FakeNode*>(relative); if (fake->title().startsWith("Porting")) porting = true; } QString name = marker->plainFullName(relative); if (!porting && !name.startsWith("Q3")) { if (obsoleteLinks) { relative->doc().location().warning(tr("Link to obsolete item '%1' in %2") .arg(atom->string()) .arg(name)); } inObsoleteLink = true; } } } } else { qDebug() << "Link to Obsolete entity" << (*node)->name() << "no relative"; } } #if 0 else if ((*node)->status() == Node::Deprecated) { qDebug() << "Link to Deprecated entity"; } else if ((*node)->status() == Node::Internal) { qDebug() << "Link to Internal entity"; } #endif } while (!path.isEmpty()) { targetAtom = myTree->findTarget(path.first(), *node); if (targetAtom == 0) break; path.removeFirst(); } if (path.isEmpty()) { link = linkForNode(*node, relative); if (*node && (*node)->subType() == Node::Image) link = "images/used-in-examples/" + link; if (targetAtom) link += "#" + refForAtom(targetAtom, *node); } } return link; } void HtmlGenerator::generateDcf(const QString &fileBase, const QString &startPage, const QString &title, DcfSection &dcfRoot) { dcfRoot.ref = startPage; dcfRoot.title = title; generateDcfSections(dcfRoot, outputDir() + "/" + fileBase + ".dcf", fileBase + "/reference"); } void HtmlGenerator::generateIndex(const QString &fileBase, const QString &url, const QString &title) { myTree->generateIndex(outputDir() + "/" + fileBase + ".index", url, title); } void HtmlGenerator::generateStatus(const Node *node, CodeMarker *marker) { Text text; switch (node->status()) { case Node::Obsolete: if (node->isInnerNode()) Generator::generateStatus(node, marker); break; case Node::Compat: if (node->isInnerNode()) { text << Atom::ParaLeft << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD) << "This " << typeString(node) << " is part of the Qt 3 support library." << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " It is provided to keep old source code working. " << "We strongly advise against " << "using it in new code. See "; const FakeNode *fakeNode = myTree->findFakeNodeByTitle("Porting To Qt 4"); Atom *targetAtom = 0; if (fakeNode && node->type() == Node::Class) { QString oldName(node->name()); targetAtom = myTree->findTarget(oldName.replace("3", ""), fakeNode); } if (targetAtom) { text << Atom(Atom::Link, linkForNode(fakeNode, node) + "#" + refForAtom(targetAtom, fakeNode)); } else text << Atom(Atom::Link, "Porting to Qt 4"); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, "Porting to Qt 4") << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << " for more information." << Atom::ParaRight; } generateText(text, node, marker); break; default: Generator::generateStatus(node, marker); } } void HtmlGenerator::generateMacRef(const Node *node, CodeMarker *marker) { if (!pleaseGenerateMacRef || marker == 0) return; QStringList macRefs = marker->macRefsForNode(node); foreach (const QString &macRef, macRefs) out() << "<a name=\"" << "//apple_ref/" << macRef << "\"></a>\n"; } void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative, CodeMarker *marker) { Q_UNUSED(marker) Q_UNUSED(relative) this->link = link; if (link.isEmpty()) { if (showBrokenLinks) out() << "<i>"; } else if (node == 0 || (relative != 0 && node->status() == relative->status())) { out() << "<a href=\"" << link << "\">"; } else { switch (node->status()) { case Node::Obsolete: out() << "<a href=\"" << link << "\" class=\"obsolete\">"; break; case Node::Compat: out() << "<a href=\"" << link << "\" class=\"compat\">"; break; default: out() << "<a href=\"" << link << "\">"; } } inLink = true; } void HtmlGenerator::endLink() { if (inLink) { if (link.isEmpty()) { if (showBrokenLinks) out() << "</i>"; } else { if (inObsoleteLink) { out() << "<sup>(obsolete)</sup>"; } out() << "</a>"; } } inLink = false; inObsoleteLink = false; } #ifdef QDOC_QML /*! Generates the summary for the \a section. Only used for sections of QML element documentation. Currently handles only the QML property group. */ void HtmlGenerator::generateQmlSummary(const Section& section, const Node *relative, CodeMarker *marker) { if (!section.members.isEmpty()) { NodeList::ConstIterator m; int count = section.members.size(); bool twoColumn = false; if (section.members.first()->type() == Node::QmlProperty) { twoColumn = (count >= 5); } if (twoColumn) out() << "<table class=\"qmlsummary\">\n" << "<tr><td class=\"topAlign\">"; out() << "<ul>\n"; int row = 0; m = section.members.begin(); while (m != section.members.end()) { if (twoColumn && row == (int) (count + 1) / 2) out() << "</ul></td><td class=\"topAlign\"><ul>\n"; out() << "<li><div class=\"fn\"></div>"; generateQmlItem(*m,relative,marker,true); out() << "</li>\n"; row++; ++m; } out() << "</ul>\n"; if (twoColumn) out() << "</td></tr>\n</table>\n"; } } /*! Outputs the html detailed documentation for a section on a QML element reference page. */ void HtmlGenerator::generateDetailedQmlMember(const Node *node, const InnerNode *relative, CodeMarker *marker) { const QmlPropertyNode* qpn = 0; generateMacRef(node, marker); out() << "<div class=\"qmlitem\">"; if (node->subType() == Node::QmlPropertyGroup) { const QmlPropGroupNode* qpgn = static_cast<const QmlPropGroupNode*>(node); NodeList::ConstIterator p = qpgn->childNodes().begin(); out() << "<div class=\"qmlproto\">"; out() << "<table class=\"qmlname\">"; while (p != qpgn->childNodes().end()) { if ((*p)->type() == Node::QmlProperty) { qpn = static_cast<const QmlPropertyNode*>(*p); out() << "<tr><td>"; out() << "<a name=\"" + refForNode(qpn) + "\"></a>"; if (!qpn->isWritable()) out() << "<span class=\"qmlreadonly\">read-only&nbsp;</span>"; if (qpgn->isDefault()) out() << "<span class=\"qmldefault\">default&nbsp;</span>"; generateQmlItem(qpn, relative, marker, false); out() << "</td></tr>"; if (qpgn->isDefault()) { out() << "</table>" << "</div></div>" << "<div class=\"qmlitem\">" << "<div class=\"qmlproto\">" << "<table class=\"qmlname\">" << "<tr><td>default</td></tr>"; } } ++p; } out() << "</table>"; out() << "</div>"; } else if (node->type() == Node::QmlSignal) { const FunctionNode* qsn = static_cast<const FunctionNode*>(node); out() << "<div class=\"qmlproto\">"; out() << "<table class=\"qmlname\">"; out() << "<tr><td>"; out() << "<a name=\"" + refForNode(qsn) + "\"></a>"; generateSynopsis(qsn,relative,marker,CodeMarker::Detailed,false); //generateQmlItem(qsn,relative,marker,false); out() << "</td></tr>"; out() << "</table>"; out() << "</div>"; } else if (node->type() == Node::QmlMethod) { const FunctionNode* qmn = static_cast<const FunctionNode*>(node); out() << "<div class=\"qmlproto\">"; out() << "<table class=\"qmlname\">"; out() << "<tr><td>"; out() << "<a name=\"" + refForNode(qmn) + "\"></a>"; generateSynopsis(qmn,relative,marker,CodeMarker::Detailed,false); out() << "</td></tr>"; out() << "</table>"; out() << "</div>"; } out() << "<div class=\"qmldoc\">"; generateStatus(node, marker); generateBody(node, marker); generateThreadSafeness(node, marker); generateSince(node, marker); generateAlsoList(node, marker); out() << "</div>"; out() << "</div>"; } /*! Output the "Inherits" line for the QML element, if there should be one. */ void HtmlGenerator::generateQmlInherits(const QmlClassNode* cn, CodeMarker* marker) { if (cn && !cn->links().empty()) { if (cn->links().contains(Node::InheritsLink)) { QPair<QString,QString> linkPair; linkPair = cn->links()[Node::InheritsLink]; QStringList strList(linkPair.first); const Node* n = myTree->findNode(strList,Node::Fake); if (n && n->subType() == Node::QmlClass) { const QmlClassNode* qcn = static_cast<const QmlClassNode*>(n); out() << "<p class=\"centerAlign\">"; Text text; text << "[Inherits "; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, linkPair.second); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << "]"; generateText(text, cn, marker); out() << "</p>"; } } } } /*! Output the "Inherit by" list for the QML element, if it is inherited by any other elements. */ void HtmlGenerator::generateQmlInheritedBy(const QmlClassNode* cn, CodeMarker* marker) { if (cn) { NodeList subs; QmlClassNode::subclasses(cn->name(),subs); if (!subs.isEmpty()) { Text text; text << Atom::ParaLeft << "Inherited by "; appendSortedQmlNames(text,cn,subs,marker); text << Atom::ParaRight; generateText(text, cn, marker); } } } /*! Output the "[Xxx instantiates the C++ class QmlGraphicsXxx]" line for the QML element, if there should be one. If there is no class node, or if the class node status is set to Node::Internal, do nothing. */ void HtmlGenerator::generateQmlInstantiates(const QmlClassNode* qcn, CodeMarker* marker) { const ClassNode* cn = qcn->classNode(); if (cn && (cn->status() != Node::Internal)) { out() << "<p class=\"centerAlign\">"; Text text; text << "["; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, qcn->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << " instantiates the C++ class "; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, cn->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << "]"; generateText(text, qcn, marker); out() << "</p>"; } } /*! Output the "[QmlGraphicsXxx is instantiated by QML element Xxx]" line for the class, if there should be one. If there is no QML element, or if the class node status is set to Node::Internal, do nothing. */ void HtmlGenerator::generateInstantiatedBy(const ClassNode* cn, CodeMarker* marker) { if (cn && cn->status() != Node::Internal && !cn->qmlElement().isEmpty()) { const Node* n = myTree->root()->findNode(cn->qmlElement(),Node::Fake); if (n && n->subType() == Node::QmlClass) { out() << "<p class=\"centerAlign\">"; Text text; text << "["; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, cn->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << " is instantiated by QML element "; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(n)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, n->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << "]"; generateText(text, cn, marker); out() << "</p>"; } } } /*! Generate the <page> element for the given \a node using the \a writer. Return true if a <page> element was written; otherwise return false. */ bool HtmlGenerator::generatePageElement(QXmlStreamWriter& writer, const Node* node, CodeMarker* marker) const { if (node->pageType() == Node::NoPageType) return false; if (node->name().isEmpty()) return true; if (node->access() == Node::Private) return false; if (!node->isInnerNode()) return false; QString title; QString rawTitle; QString fullTitle; const InnerNode* inner = static_cast<const InnerNode*>(node); writer.writeStartElement("page"); QXmlStreamAttributes attributes; QString t; t.setNum(id++); switch (node->type()) { case Node::Fake: { const FakeNode* fake = static_cast<const FakeNode*>(node); title = fake->fullTitle(); break; } case Node::Class: { title = node->name() + " Class Reference"; break; } case Node::Namespace: { rawTitle = marker->plainName(inner); fullTitle = marker->plainFullName(inner); title = rawTitle + " Namespace Reference"; break; } default: title = node->name(); break; } writer.writeAttribute("id",t); writer.writeStartElement("pageWords"); writer.writeCharacters(title); if (!inner->pageKeywords().isEmpty()) { const QStringList& w = inner->pageKeywords(); for (int i = 0; i < w.size(); ++i) { writer.writeCharacters(" "); writer.writeCharacters(w.at(i).toLocal8Bit().constData()); } } writer.writeEndElement(); writer.writeStartElement("pageTitle"); writer.writeCharacters(title); writer.writeEndElement(); writer.writeStartElement("pageUrl"); writer.writeCharacters(PageGenerator::fileName(node)); writer.writeEndElement(); writer.writeStartElement("pageType"); switch (node->pageType()) { case Node::ApiPage: writer.writeCharacters("APIPage"); break; case Node::ArticlePage: writer.writeCharacters("Article"); break; case Node::ExamplePage: writer.writeCharacters("Example"); break; default: break; } writer.writeEndElement(); writer.writeEndElement(); return true; } /*! Traverse the tree recursively and generate the <keyword> elements. */ void HtmlGenerator::generatePageElements(QXmlStreamWriter& writer, const Node* node, CodeMarker* marker) const { if (generatePageElement(writer, node, marker)) { if (node->isInnerNode()) { const InnerNode *inner = static_cast<const InnerNode *>(node); // Recurse to write an element for this child node and all its children. foreach (const Node *child, inner->childNodes()) generatePageElements(writer, child, marker); } } } /*! Outputs the file containing the index used for searching the html docs. */ void HtmlGenerator::generatePageIndex(const QString& fileName, CodeMarker* marker) const { QFile file(fileName); if (!file.open(QFile::WriteOnly | QFile::Text)) return ; QXmlStreamWriter writer(&file); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement("qtPageIndex"); generatePageElements(writer, myTree->root(), marker); writer.writeEndElement(); // qtPageIndex writer.writeEndDocument(); file.close(); } #endif #if 0 // fossil removed for new doc format MWS 19/04/2010 out() << "<!DOCTYPE html\n" " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n"; out() << QString("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"%1\" lang=\"%1\">\n").arg(naturalLanguage); QString shortVersion; if ((project != "Qtopia") && (project != "Qt Extended")) { shortVersion = project + " " + shortVersion + ": "; if (node && !node->doc().location().isEmpty()) out() << "<!-- " << node->doc().location().fileName() << " -->\n"; shortVersion = myTree->version(); if (shortVersion.count(QChar('.')) == 2) shortVersion.truncate(shortVersion.lastIndexOf(QChar('.'))); if (!shortVersion.isEmpty()) { if (project == "QSA") shortVersion = "QSA " + shortVersion + ": "; else shortVersion = "Qt " + shortVersion + ": "; } } out() << "<head>\n" " <title>" << shortVersion << protectEnc(title) << "</title>\n"; out() << QString("<meta http-equiv=\"Content-type\" content=\"text/html; charset=%1\" />").arg(outputEncoding); if (!style.isEmpty()) out() << " <style type=\"text/css\">" << style << "</style>\n"; const QMap<QString, QString> &metaMap = node->doc().metaTagMap(); if (!metaMap.isEmpty()) { QMapIterator<QString, QString> i(metaMap); while (i.hasNext()) { i.next(); out() << " <meta name=\"" << protectEnc(i.key()) << "\" contents=\"" << protectEnc(i.value()) << "\" />\n"; } } navigationLinks.clear(); if (node && !node->links().empty()) { QPair<QString,QString> linkPair; QPair<QString,QString> anchorPair; const Node *linkNode; if (node->links().contains(Node::PreviousLink)) { linkPair = node->links()[Node::PreviousLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " <link rel=\"prev\" href=\"" << anchorPair.first << "\" />\n"; navigationLinks += "[Previous: <a href=\"" + anchorPair.first + "\">"; if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) navigationLinks += protectEnc(anchorPair.second); else navigationLinks += protectEnc(linkPair.second); navigationLinks += "</a>]\n"; } if (node->links().contains(Node::ContentsLink)) { linkPair = node->links()[Node::ContentsLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " <link rel=\"contents\" href=\"" << anchorPair.first << "\" />\n"; navigationLinks += "[<a href=\"" + anchorPair.first + "\">"; if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) navigationLinks += protectEnc(anchorPair.second); else navigationLinks += protectEnc(linkPair.second); navigationLinks += "</a>]\n"; } if (node->links().contains(Node::NextLink)) { linkPair = node->links()[Node::NextLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " <link rel=\"next\" href=\"" << anchorPair.first << "\" />\n"; navigationLinks += "[Next: <a href=\"" + anchorPair.first + "\">"; if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) navigationLinks += protectEnc(anchorPair.second); else navigationLinks += protectEnc(linkPair.second); navigationLinks += "</a>]\n"; } if (node->links().contains(Node::IndexLink)) { linkPair = node->links()[Node::IndexLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " <link rel=\"index\" href=\"" << anchorPair.first << "\" />\n"; } if (node->links().contains(Node::StartLink)) { linkPair = node->links()[Node::StartLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " <link rel=\"start\" href=\"" << anchorPair.first << "\" />\n"; } } foreach (const QString &stylesheet, stylesheets) { out() << " <link href=\"" << stylesheet << "\" rel=\"stylesheet\" " << "type=\"text/css\" />\n"; } foreach (const QString &customHeadElement, customHeadElements) { out() << " " << customHeadElement << "\n"; } out() << "</head>\n" #endif QT_END_NAMESPACE