To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Revision:

root / import / Import.cpp

History | View | Annotate | Download (22.7 KB)

1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2

    
3
#include "Objects.h"
4

    
5
#include <dataquay/BasicStore.h>
6
#include <dataquay/RDFException.h>
7
#include <dataquay/objectmapper/ObjectStorer.h>
8
#include <dataquay/objectmapper/ObjectLoader.h>
9
#include <dataquay/objectmapper/ObjectBuilder.h>
10
#include <dataquay/objectmapper/TypeMapping.h>
11
#include <dataquay/objectmapper/ContainerBuilder.h>
12

    
13
#include "ImportClassicalComposersOrg.h"
14
#include "ImportClassicalDotNet.h"
15
#include "ImportClassicalArchives.h"
16
#include "ImportWikipediaComposers.h"
17
#include "ImportWikipediaWorks.h"
18
#include "ImportWikipediaWorksK.h"
19
#include "ImportWikipediaWorksList.h"
20
#include "ImportHoboken.h"
21

    
22
#include "TypeRegistrar.h"
23

    
24
#include <dataquay/Debug.h>
25

    
26
using namespace ClassicalData;
27
using namespace Dataquay;
28

    
29
#include <iostream>
30
#include <set>
31

    
32
typedef QMap<QString, QSet<Composer *> > ComposerMap; // name -> composers
33

    
34
void
35
addMiscExpansions(Composer *c)
36
{
37
    QString n = c->name();
38

    
39
    DEBUG << "addMiscExpansions: n = " << n << endl;
40

    
41
    // lovely hard-coded special cases go here! some of these are
42
    // needed for works->composer assignments
43
    if (n == "Balakirev, Milii") {
44
        c->addAlias("Mily Balakirev");
45
    }
46
    if (n.startsWith("Cui, C")) {
47
        c->addAlias(QString::fromUtf8("C\303\251sar Cui"));
48
    }
49
    if (n == "Handel, George Frideric") {
50
        c->addAlias("Handel, Georg Friedrich");
51
        c->addAlias("Handel");
52
    }
53
    if (n == "Prokofiev, Sergey") {
54
        c->addAlias("Prokofieff, Sergei");
55
        c->addAlias("Sergei Prokofieff");
56
    }
57
    if (n == "Rossini, Gioacchino") {
58
        c->addAlias("Rossini, Gioachino");
59
        c->addAlias("Gioachino Rossini");
60
    }
61
    if (n == "Edwards, Richard") {
62
        c->addAlias("Edwardes, Richard");
63
        c->addAlias("Richard Edwardes");
64
        c->addAlias("Richard Edwards");
65
    }
66
    if (n == "Rimsky-Korsakov, Nikolay Andreyevich") {
67
        c->addAlias("Nikolai Rimsky-Korsakov");
68
    }
69
    if (n.startsWith("Piccinni, Nico")) {
70
        c->addAlias(n);
71
        c->setName(QString::fromUtf8("Piccinni, Niccol\303\262"));
72
    }
73
    if (n == "Tchaikovsky, Pyotr Ilyich") {
74
        c->addAlias("Tchaikovsky, Piotr Ilyitch");
75
    }
76
    if (n == "Wilhelm Stenhammar") {
77
        c->addAlias("Stenhammar, Vilhelm Eugene");
78
        c->setName("Stenhammar, Wilhelm");
79
        c->addAlias(n);
80
    }
81
    if (n == "Mercadante, Saverio Rafaele") {
82
        c->addAlias("Mercadante, Giuseppe");
83
    }
84
    if (n == "Johann Wenzel Anton Stamitz") {
85
        c->addAlias(n);
86
        c->setName("Stamitz, Johann Wenzel Anton");
87
        c->addAlias("Stamitz, Jan Vaclav");
88
    }
89
    if (n == "Mario Castelnuovo-Tedesco") {
90
        c->addAlias("Castelnuovo Tedesco, Mario");
91
    }
92
    if (n == "Mayr, Simon") {
93
        c->addAlias("Mayr");
94
    }
95

    
96
    n.replace(", Sr.", " Sr.");
97
    n.replace(", Jr.", " Jr.");
98

    
99
    int comma = n.indexOf(", ");
100
    if (comma > 0 && comma + 2 < n.length()) {
101

    
102
        QString left = n.left(comma);
103
        QString right = n.right(n.length() - comma - 2);
104

    
105
        QRegExp jrsr("( (Sr\\.|Jr\\.|I|II))$");
106
        if (jrsr.indexIn(right) >= 0) {
107
            left = left + jrsr.cap(1);
108
            right = right.left(right.length()-jrsr.matchedLength());
109
        }
110
        n = right + " " + left;
111
    }
112

    
113
    if (n != c->name()) c->addAlias(n);
114

    
115
    if (n.contains("Sergey")) {
116
        QString nn(n);
117
        nn.replace("Sergey", "Sergei");
118
        c->addAlias(nn);
119
    } else if (n.contains("Sergei")) {
120
        QString nn(n);
121
        nn.replace("Sergei", "Sergey");
122
        c->addAlias(nn);
123
    }
124

    
125
    QRegExp sr("((, )?Sr\\.|Senior|\\(?the elder\\)?)", Qt::CaseInsensitive);
126
    if (sr.indexIn(n) >= 0) {
127
        QString nr = n;
128
        nr.replace(sr.pos(0), sr.matchedLength(), " I");
129
        nr.replace("  ", " ");
130
        DEBUG << "addMiscExpansions: trying " << nr << " for " << n << endl;
131
        c->addAlias(nr);
132
    }
133
    QRegExp jr("((, )?Jr\\.|Junior|\\(?the younger\\)?)", Qt::CaseInsensitive);
134
    if (jr.indexIn(n) >= 0) {
135
        QString nr = n;
136
        nr.replace(jr.pos(0), jr.matchedLength(), " II");
137
        nr.replace("  ", " ");
138
        DEBUG << "addMiscExpansions: trying " << nr << " for " << n << endl;
139
        c->addAlias(nr);
140
    }
141
    QString nr = n;
142
    nr.replace("(I)", "I");
143
    nr.replace("(II)", "II");
144
    nr.replace("(III)", "III");
145
    c->addAlias(nr);
146
}
147

    
148
bool
149
hasBetterName(Composer *c, Composer *other)
150
{
151
    if (c->name() == other->name()) return false;
152

    
153
    // Try to guess which of c and other is more likely to have a good
154
    // "canonical form" of the composer's name
155

    
156
    if (c->name().startsWith("van ")) {
157
        return false; // wrong choice of sort for e.g. LvB; should be
158
                      // Beethoven, Ludwig van, not van Beethoven, Ludwig
159
    }
160
    if (other->name().startsWith("van ")) {
161
        return true;
162
    }
163

    
164
    if (c->aliases().size() != other->aliases().size()) {
165
        // a rather weak heuristic
166
        return c->aliases().size() > other->aliases().size();
167
    }
168

    
169
    if (c->name().contains(',') && !other->name().contains(',')) {
170
        // another rather weak heuristic
171
        return true;
172
    }
173

    
174
    return false;
175
}
176

    
177
void mergeComposer(Composer *c, ComposerMap &composers)
178
{
179
    QString name = c->name();
180

    
181
    QSet<QString> allNames = c->aliases();
182
    allNames.insert(name);
183
    
184
    QString dates;
185
    if (c->birth()) {
186
        if (c->death()) {
187
            dates = QString("%1-%2").arg(c->birth()->year()).arg(c->death()->year());
188
        } else {
189
            dates = QString("%1-").arg(c->birth()->year());
190
        }
191
    }
192
    if (dates != "") {
193
        allNames.insert(dates);
194
    }
195

    
196
    QSet<Composer *> matches;
197

    
198
    foreach (QString candidateName, allNames) {
199
        QString key = Composer::reduceName(candidateName);
200
        if (composers.contains(key)) {
201
            foreach (Composer *candidate, composers[key]) {
202
                if (candidateName == dates) {
203
                    if (c->name() == candidate->name()) {
204
                        DEBUG << "mergeComposer: Exact name match for " << c->name() << " with date(s) " << dates << endl;
205
                    } else if (!candidate->matchCatalogueName(c->name()) &&
206
                               !c->matchCatalogueName(candidate->name())) {
207
                        DEBUG << "mergeComposer: Names differ for " << c->name() << " and " << candidate->name() << " (having matched date(s) " << dates << ")" << endl;
208
                        continue;
209
                    } else {
210
                        DEBUG << "mergeComposer: Note: Fuzzy name match for " << c->name() << " and " << candidate->name() << " with date(s) " << dates << endl;
211
                    }
212
                } else {
213
                    if (!c->matchDates(candidate)) {
214
                        DEBUG << "mergeComposer: Dates differ for " << c->name() << " and " << candidate->name() << endl;
215
                        continue;
216
                    }
217
                }
218
                matches.insert(candidate);
219
            }
220
        }
221
    }
222

    
223
    if (matches.empty()) {
224
        DEBUG << "mergeComposer: No existing composer with alias matching any alias of " << c->name() << ", adding" << endl;
225

    
226
        if (!c->birth() && !c->death()) {
227
            DEBUG << "Composer has no dates, laboriously searching for all names" << endl;
228
            // laboriously look for fuzzy match across _all_ composers
229
            for (ComposerMap::iterator i = composers.begin();
230
                 i != composers.end(); ++i) {
231
                foreach (Composer *candidate, *i) {
232
                    if (candidate->matchCatalogueName(c->name())) {
233
                        DEBUG << "mergeComposer: Found fuzzy match for undated composer " << c->name() << " as " << candidate->name() << ", daringly merging" << endl;
234
                        matches.insert(candidate);
235
                        break;
236
                    }
237
                }
238
                if (!matches.empty()) break;
239
            }
240
        }
241

    
242
        if (matches.empty()) {
243
            foreach (QString candidateName, allNames) {
244
                QString key = Composer::reduceName(candidateName);
245
                composers[key].insert(c);
246
                DEBUG << "added for alias or date " << candidateName << endl;
247
            }
248
            return;
249
        }
250
    }
251

    
252
    if (matches.size() > 1) {
253
        DEBUG << "mergeComposer: More than one composer matches name and date(s) for " << c->name() << " -- something fishy here" << endl;
254
    }
255

    
256
    Composer *other = *matches.begin();
257

    
258
    DEBUG << "mergeComposer: Merging " << c->name() << " with " << other->name() << endl;
259

    
260
    if (hasBetterName(c, other)) {
261
        other->addAlias(other->name());
262
        other->setName(c->name());
263
    } else {
264
        other->addAlias(c->name());
265
    }
266
    composers[Composer::reduceName(c->name())].insert(other);
267
    DEBUG << "linking from alias " << c->name() << endl;
268

    
269
    foreach (QString alias, c->aliases()) {
270
        if (alias != other->name() && 
271
            !other->aliases().contains(alias)) {
272
            other->addAlias(alias);
273
            composers[Composer::reduceName(alias)].insert(other);
274
            DEBUG << "linking from alias " << alias << endl;
275
        }
276
    }
277
    
278
    foreach (Document *d, c->pages()) {
279
        bool found = false;
280
        foreach (Document *dd, other->pages()) {
281
            if (d->uri() == dd->uri()) {
282
                found = true;
283
                break;
284
            }
285
        }
286
        if (!found) {
287
            d->setTopic(other);
288
            other->addPage(d);
289
        }
290
    }
291

    
292
    //!!! actually the "approximate" bits of the following are bogus;
293
    // a source reporting birth or death date as approx is probably
294
    // more accurate than one reporting an exact date
295

    
296
    if (c->birth()) {
297
        if (!other->birth() || other->birth()->approximate()) {
298
            other->setBirth(c->birth());
299
        }
300
    }
301

    
302
    if (c->death()) {
303
        if (!other->death() || other->death()->approximate()) {
304
            other->setDeath(c->death());
305
        }
306
    }
307

    
308
    if (c->gender() != "") other->setGender(c->gender());
309

    
310
    foreach (QString s, c->nationality()) {
311
        other->addNationality(s);
312
    }
313

    
314
    foreach (Uri s, c->geonameURIs()) {
315
        other->addGeonameURI(s);
316
    }
317

    
318
    if (c->remarks() != "") other->setRemarks(c->remarks());
319
    if (c->period() != "") other->setPeriod(c->period());
320

    
321
}
322
    
323
QString
324
asciify(QString field)
325
{
326
    // accented characters etc -- add "ascii version" for dumb search purposes
327
    QString ascii;
328
    for (int i = 0; i < field.length(); ++i) {
329
        QString dc = field[i].decomposition();
330
        if (dc != "") ascii += dc[0];
331
        else if (field[i] == QChar(0x00DF)) {
332
            ascii += "ss";
333
        } else {
334
            ascii += field[i];
335
        }
336
    }
337
    ascii.replace(QString::fromUtf8("\342\200\231"), "'"); // apostrophe
338
    ascii.replace(QString::fromUtf8("\342\200\222"), "-");
339
    ascii.replace(QString::fromUtf8("\342\200\223"), "-");
340
    ascii.replace(QString::fromUtf8("\342\200\224"), "-");
341
    ascii.replace(QString::fromUtf8("\342\200\225"), "-");
342
    return ascii;
343
}
344

    
345
void
346
asciify(Composer *c)
347
{
348
    QString n = c->name();
349
    QString asc = asciify(n);
350
    if (asc != n && !c->aliases().contains(asc)) c->addAlias(asc);
351
    foreach (QString alias, c->aliases()) {
352
        asc = asciify(alias);
353
        if (asc != alias && !c->aliases().contains(asc)) c->addAlias(asc);
354
    }
355
}
356

    
357
void
358
asciify(Work *w)
359
{
360
    QString n = w->name();
361
    QString asc = asciify(n);
362
    if (asc != n && !w->aliases().contains(asc)) w->addAlias(asc);
363
    foreach (QString alias, w->aliases()) {
364
        asc = asciify(alias);
365
        if (asc != alias && !w->aliases().contains(asc)) w->addAlias(asc);
366
    }
367
}
368

    
369
void
370
assignUri(Store *s, Composer *c)
371
{
372
    static QSet<QString> convSet;
373
    QString conv = c->name();
374
    if (!conv.contains(",")) {
375
        QStringList sl = conv.split(" ");
376
        if (!sl.empty()) {
377
            sl.push_front(sl[sl.size()-1]);
378
            sl.removeLast();
379
            conv = sl.join(" ");
380
            DEBUG << "assignUri: " << c->name() << " -> " << conv << endl;
381
        }
382
    }
383
    conv = asciify(conv);
384
    conv.replace(" ", "_");
385
    conv.replace("-", "_");
386
    conv.replace(QRegExp("[^a-zA-Z0-9_-]"), "");
387
    conv = conv.toLower();
388
    QString initial = conv;
389
    int i = 2;
390
    while (convSet.contains(conv)) {
391
        conv = QString("%1__%2").arg(initial).arg(i);
392
        i++;
393
    }
394
    convSet.insert(conv);
395
    c->setProperty("uri", QVariant::fromValue(s->expand(":composer/" + conv)));
396
}
397

    
398
void
399
assignUri(Store *s, Work *w, Composer *c)
400
{
401
    QString pfx = c->property("uri").value<Uri>().toString();
402
    DEBUG << "pfx = " << pfx << endl;
403
    if (!pfx.contains("composer/")) pfx = ":work/";
404
    else {
405
        pfx.replace("composer/", "work/");
406
        pfx += "/";
407
    }
408

    
409
    static QSet<QString> convSet;
410

    
411
    QString conv = w->catalogue();
412
    if (conv == "") conv = w->opus();
413
    conv = conv.replace(".", "");
414
    bool hasOpus = (conv != "");
415
    if (conv == "") conv = w->name().toLower();
416
    if (w->number() != "") conv = conv + "_no" + w->number();
417
    conv = asciify(conv);
418
    conv.replace(" ", "_");
419
    conv.replace("-", "_");
420
    conv.replace(":", "_");
421
    conv.replace(QRegExp("[^a-zA-Z0-9_-]"), "");
422

    
423
    if (pfx != "") conv = pfx + conv;
424

    
425
    // I think actually for works we want to merge duplicates rather than
426
    // assign them separate URIs, _unless_ they lack a viable opus number
427
    if (!hasOpus) {
428
        QString initial = conv;
429
        int i = 2;
430
        while (convSet.contains(conv)) {
431
            conv = QString("%1__%2").arg(initial).arg(i);
432
            i++;
433
        }
434
    }
435
    convSet.insert(conv);
436

    
437
    w->setProperty("uri", conv);
438
}
439

    
440
void
441
addDbpediaResource(Store *store, QObject *o, QString s)
442
{
443
    Uri u = o->property("uri").value<Uri>();
444
    if (u == Uri()) return;
445
    if (s.startsWith("http://en.wikipedia.org/wiki/")) {
446
        store->add(Triple(u,
447
                          "mo:wikipedia",
448
                          Uri(s)));
449
        s.replace("http://en.wikipedia.org/wiki/",
450
                  "http://dbpedia.org/resource/");
451
        store->add(Triple(u,
452
                          "owl:sameAs",
453
                          Uri(s)));
454
    }
455
}
456

    
457
int main(int argc, char **argv)
458
{
459
    qRegisterMetaType<ClassicalComposersOrgImporter *>
460
        ("ClassicalData::ClassicalComposersOrgImporter*");
461
    qRegisterMetaType<ClassicalDotNetImporter *>
462
        ("ClassicalData::ClassicalDotNetImporter*");
463
    qRegisterMetaType<ClassicalArchivesImporter *>
464
        ("ClassicalData::ClassicalArchivesImporter*");
465
    qRegisterMetaType<WikipediaComposersImporter *>
466
        ("ClassicalData::WikipediaComposersImporter*");
467
    qRegisterMetaType<WikipediaWorksImporter *>
468
        ("ClassicalData::WikipediaWorksImporter*");
469
    qRegisterMetaType<WikipediaWorksKImporter *>
470
        ("ClassicalData::WikipediaWorksKImporter*");
471
    qRegisterMetaType<WikipediaWorksListImporter *>
472
        ("ClassicalData::WikipediaWorksListImporter*");
473
    qRegisterMetaType<HobokenImporter *>
474
        ("ClassicalData::HobokenImporter*");
475

    
476
    ObjectBuilder::getInstance()->registerClass
477
        <ClassicalComposersOrgImporter>("ClassicalData::ClassicalComposersOrgImporter*");
478
    ObjectBuilder::getInstance()->registerClass
479
        <ClassicalDotNetImporter>("ClassicalData::ClassicalDotNetImporter*");
480
    ObjectBuilder::getInstance()->registerClass
481
        <ClassicalArchivesImporter>("ClassicalData::ClassicalArchivesImporter*");
482
    ObjectBuilder::getInstance()->registerClass
483
        <WikipediaComposersImporter>("ClassicalData::WikipediaComposersImporter*");
484
    ObjectBuilder::getInstance()->registerClass
485
        <WikipediaWorksImporter>("ClassicalData::WikipediaWorksImporter*");
486
    ObjectBuilder::getInstance()->registerClass
487
        <WikipediaWorksKImporter>("ClassicalData::WikipediaWorksKImporter*");
488
    ObjectBuilder::getInstance()->registerClass
489
        <WikipediaWorksListImporter>("ClassicalData::WikipediaWorksListImporter*");
490
    ObjectBuilder::getInstance()->registerClass
491
        <HobokenImporter>("ClassicalData::HobokenImporter*");
492

    
493
    BasicStore *store = BasicStore::load(QUrl("file:importers.ttl"));
494
    ObjectLoader loader(store);
495
    QObject *parentObject = loader.loadAllObjects(new QObject());
496
    
497
    BasicStore *outstore = new BasicStore();
498
    outstore->setBaseUri(Uri("http://dbtune.org/classical/resource/"));
499
    ObjectStorer storer(outstore);
500
    TypeMapping tm;
501

    
502
    TypeRegistrar::registerTypes();
503
    TypeRegistrar::addMappings(outstore, &tm);
504

    
505
    storer.setTypeMapping(tm);
506
    storer.setPropertyStorePolicy(ObjectStorer::StoreIfChanged);
507
    storer.setObjectStorePolicy(ObjectStorer::StoreAllObjects);
508
    storer.setBlankNodePolicy(ObjectStorer::NoBlankNodes);
509

    
510
    QList<Importer *> importers = parentObject->findChildren<Importer *>();
511
    std::cerr << "have " << importers.size() << " importers" << std::endl;
512

    
513
    ComposerMap composers;
514

    
515
    QList<Composer *> dated;
516
    QList<Composer *> undated;
517

    
518
    QList<Work *> works;
519
    QList<Composition *> compositions;
520
    QList<QObject *> other;
521
    
522
    foreach (Importer *importer, importers) {
523
        QObjectList objects = importer->getImportedObjects();
524
        foreach (QObject *o, objects) {
525
            Composer *c;
526
            if ((c = qobject_cast<Composer *>(o))) {
527
                addMiscExpansions(c);
528
                asciify(c);
529
                if (c->birth() || c->death()) dated.push_back(c);
530
                else undated.push_back(c);
531
                continue;
532
            }
533
            Work *w;
534
            if ((w = qobject_cast<Work *>(o))) {
535
                asciify(w); 
536
                works.push_back(w);
537
                continue;
538
            }
539
            Composition *cn;
540
            if ((cn = qobject_cast<Composition *>(o))) {
541
                compositions.push_back(cn);
542
                continue;
543
            }
544
        }
545
    }
546

    
547
    // get all the dated composers merged before attempting to match
548
    // the undated ones
549
    foreach (Composer *c, dated) {
550
        mergeComposer(c, composers);
551
    }
552
    foreach (Composer *c, undated) {
553
        mergeComposer(c, composers);
554
    }
555

    
556
    QObjectList toStore;
557

    
558
    QSet<Composer *> cset;
559
    for (ComposerMap::iterator i = composers.begin(); i != composers.end(); ++i) {
560
        foreach (Composer *c, i.value()) {
561
            if (!cset.contains(c)) {
562
                assignUri(outstore, c);
563
                toStore.push_back(c);
564
                cset.insert(c);
565
            }
566
            foreach (Document *d, c->pages()) {
567
                QString s = d->uri().toString();
568
                addDbpediaResource(outstore, c, s);
569
            }                        
570
        }
571
    }
572

    
573
    QSet<QString> storedUris;
574

    
575
    foreach (Work *w, works) {
576
        Composition *cn = w->composition();
577
        if (!cn) continue;
578
        if (!cn->composer()) {
579
            QString cname = cn->composerName();
580
            QString key = Composer::reduceName(cname);
581
            if (cname != "") {
582
                if (!composers.contains(key)) {
583
                    DEBUG << "Failed to assign Composition to composer: no composer matches name " << cname << endl;
584
                } else {
585
                    QSet<Composer *> cs = composers[key];
586
                    if (cs.empty()) {
587
                        DEBUG << "Failed to assign Composition to composer: no composer matches name " << cname << endl;
588
                    } else if (cs.size() > 1) {
589
                        DEBUG << "Failed to assign Composition to composer: "
590
                              << cs.size() << " composers match name " << cname << endl;
591
                    } else {
592
                        cn->setComposer(*cs.begin());
593
                    }
594
                }
595
            } else {
596
                DEBUG << "Failed to assign Composition to composer: composer name is empty" << endl;
597
            }
598
        }
599

    
600
        if (cn->composer()) {
601
            assignUri(outstore, w, cn->composer());
602
        }
603

    
604
        foreach (Document *d, w->pages()) {
605
            QString s = d->uri().toString();
606
            addDbpediaResource(outstore, w, s);
607
            if (!storedUris.contains(s)) {
608
                toStore.push_back(d);
609
                storedUris.insert(s);
610
            }
611
        }                        
612

    
613
        QString u = w->property("uri").value<Uri>().toString();
614
        if (u == "" || !storedUris.contains(u)) {
615
            toStore.push_back(w);
616
            if (u != "") storedUris.insert(u);
617
        }
618
    }
619

    
620
    try {
621
        storer.storeAllObjects(toStore);
622
        
623
    } catch (RDFException e) {
624
        std::cerr << "Caught RDF exception: " << e.what() << std::endl;
625
    }
626

    
627
    DEBUG << "Stored, now saving" << endl;
628

    
629
    outstore->save("imported.ttl");
630

    
631
    DEBUG << "Saved" << endl;
632

    
633

    
634
    QMultiMap<QString, Composer *> cmap;
635
    foreach (Composer *c, cset) {
636
        QString n = c->getSortName(true);
637
        cmap.insert(n, c);
638
    }
639

    
640
    std::cout << "Composers: " << cmap.size() << std::endl;
641

    
642
    for (QMultiMap<QString, Composer *>::iterator i = cmap.begin();
643
         i != cmap.end(); ++i) {
644

    
645
        QString n = i.key();
646
        Composer *c = i.value();
647
        
648
        std::cout << n.toStdString();
649
        
650
        QString d = c->getDisplayDates();
651
        if (d != "") std::cout << " (" << d.toStdString() << ")";
652
        std::cout << std::endl;
653
    }
654

    
655
    std::cout << std::endl;
656

    
657
    std::cout << "Works by composer:" << std::endl;
658

    
659
    for (QMultiMap<QString, Composer *>::iterator i = cmap.begin();
660
         i != cmap.end(); ++i) {
661

    
662
        QString n = i.key();
663
        Composer *c = i.value();
664
    
665
        std::set<Work *, Work::Ordering> wmap;
666
        foreach (Work *w, works) {
667
            Composition *cn = w->composition();
668
            if (!cn) continue;
669
            if (cn->composer() != c) continue;
670
            if (w->partOf()) continue;
671
            wmap.insert(w);
672
        }
673

    
674
        if (wmap.empty()) continue;
675
        
676
        std::cout << n.toStdString() << std::endl;
677

    
678
        foreach (Work *w, wmap) {
679
            std::cout << " * ";
680
            std::cout << w->name().toStdString();
681
            if (w->catalogue() != "") {
682
                std::cout << " [" << w->catalogue().toStdString() << "]";
683
            }
684
            if (w->opus() != "") {
685
                std::cout << " [op. " << w->opus().toStdString() << "]";
686
            }
687
            std::cout << std::endl;
688
            std::set<Work *, Work::Ordering> orderedParts;
689
            foreach (Work *ww, w->parts()) {
690
                orderedParts.insert(ww);
691
            }
692
            foreach (Work *ww, orderedParts) {
693
                std::cout << "    ";
694
                if (ww->number() != "") {
695
                    std::cout << ww->number().toStdString() << ". ";
696
                }
697
                std::cout << ww->name().toStdString();
698
                if (ww->catalogue() != "" && ww->catalogue() != w->catalogue()) {
699
                    std::cout << " [" << ww->catalogue().toStdString() << "]";
700
                }
701
                if (ww->opus() != "" && ww->opus() != w->opus()) {
702
                    std::cout << " [op. " << ww->opus().toStdString() << "]";
703
                }
704
                std::cout << std::endl;
705
            }
706
        }
707

    
708
        std::cout << std::endl;
709
    }
710

    
711
    delete outstore;
712

    
713
    DEBUG << "Done" << endl;
714

    
715

    
716
}
717

    
718