From baa326c09201d76f02c28168ab4eaad427596b48 Mon Sep 17 00:00:00 2001 From: "Troy A. Griffitts" Date: Thu, 17 Feb 2022 17:42:32 +0000 Subject: Committed latest version of migrate tags made it works with both modules and modules without tags added parameter -t to specify target module git-svn-id: https://www.crosswire.org/svn/sword-tools/trunk@540 07627401-56e2-0310-80f4-f8cd0041bdcd --- migratetags/Makefile | 2 +- migratetags/matchers/defaultmatcher.h | 5 +- migratetags/migratetags.cpp | 147 ++++++++--- migratetags/tag-exceptions-na28-example.conf | 365 +++++++++++++++++++++++++++ 4 files changed, 482 insertions(+), 37 deletions(-) create mode 100644 migratetags/tag-exceptions-na28-example.conf diff --git a/migratetags/Makefile b/migratetags/Makefile index 19f4335..fcfea28 100644 --- a/migratetags/Makefile +++ b/migratetags/Makefile @@ -4,7 +4,7 @@ TARGETS= migratetags all: $(TARGETS) clean: - rm $(TARGETS) + rm -f $(TARGETS) .cpp: g++ -g `pkg-config --cflags sword` $< -o $@ `pkg-config --libs sword` diff --git a/migratetags/matchers/defaultmatcher.h b/migratetags/matchers/defaultmatcher.h index b74ed38..fbf14ff 100644 --- a/migratetags/matchers/defaultmatcher.h +++ b/migratetags/matchers/defaultmatcher.h @@ -1,12 +1,15 @@ #include "matcher.h" +#include #ifndef defaultmatcher_h #define defaultmatcher_h class DefaultMatcher : public Matcher { + UTF8GreekAccents sanitizeGreekAccentFilter; public: - DefaultMatcher() { + DefaultMatcher() : sanitizeGreekAccentFilter() { + sanitizeGreekAccentFilter.setOptionValue("off"); } // Compares 2 words and tries to give a percentage assurance of a match diff --git a/migratetags/migratetags.cpp b/migratetags/migratetags.cpp index 2b6b92b..ecb1ed0 100644 --- a/migratetags/migratetags.cpp +++ b/migratetags/migratetags.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -14,11 +15,13 @@ using namespace std; #include "matchers/matcher.h" // select your matcher here +//#include "matchers/gntmatcher.h" #include "matchers/defaultmatcher.h" Matcher *matcher = new DefaultMatcher(); -const char *targetModuleName="NA28"; -const char *strongsSourceModuleName="WHNU"; +// hard code your from and to modules here or pass them on the command line with - +SWBuf strongsSourceModuleName = "WHNU"; +SWBuf targetModuleName = "NA28FromImp"; const char *ignoreSeries = "⸆¹⸆²⸆⸇᾿˸¹˸²˸³˸·¹²⟦–ʹ°¹°²⸋¹⸋²⸋⸌¹⸌°*[];⸀¹⸀²⸀³⸁⸀◆⟧ ⸂¹⸂²⸄⸂⸅⸃⸉¹⸈⸉⸊ "; @@ -30,12 +33,14 @@ void insert(SWBuf addText, SWBuf &out, int bibPos, BibMap &bibMap, BibMap &wTags SWBuf findCanonicalBibleText(SWBuf orig, BibMap &bibMap, BibMap &tTags); SWBuf buildWordMaps(const SWBuf &markupBuf, const BibMap &bibMap, vector &targetWords, vector &targetWordStarts, vector &targetWordEnds); void pullFromModData(SWModule &fromMod, vector&wordTags, vector &fromWords, vector &fromWordTags); -void insertWordTags(SWBuf &markupBuf, BibMap &bibMap, BibMap &wTags, const vector &targetWordTags, const vector &wordTags, const vector &targetWordStarts, const vector &targetWordEnds); +void insertWordTags(VerseKey *vk, SWBuf &markupBuf, BibMap &bibMap, BibMap &wTags, vector &targetWordTags, const vector &wordTags, const vector &targetWordStarts, const vector &targetWordEnds); // app options bool optionFilterAccents = false; bool optionFilterAppCrit = false; bool optionDebug = false; +vector optionExceptionFile; +SWConfig *exceptionFile = 0; void usage(const char *progName, const char *error = 0) { if (error) fprintf(stderr, "\n%s: %s\n", progName, error); @@ -43,6 +48,10 @@ void usage(const char *progName, const char *error = 0) { fprintf(stderr, "\nusage: %s [options]\n", progName); fprintf(stderr, " -v\t\t\t verbose: print lots of information while processing\n"); fprintf(stderr, " -fa\t\t\t filter accents: remove Greek accents from final text\n"); + fprintf(stderr, " -fc\t\t\t filter critical apparatus markers from final text\n"); + fprintf(stderr, " -ss \t provide the Strong's source module name\n"); + fprintf(stderr, " -t \t provide the target module name\n"); + fprintf(stderr, " -e \t provide an ini-style .conf file with overriding tag exceptions.\n"); fprintf(stderr, "\n\n"); exit(-1); } @@ -61,6 +70,24 @@ int main(int argc, char **argv) { else if (!strcmp(argv[i], "-fc")) { optionFilterAppCrit = true; } + else if (!strcmp(argv[i], "-ss")) { + if ((i + 1) < argc) { + strongsSourceModuleName = argv[++i]; + } + else usage(progName, "-ss argument requires a module name."); + } + else if (!strcmp(argv[i], "-t")) { + if ((i + 1) < argc) { + targetModuleName = argv[++i]; + } + else usage(progName, "-t argument requires a module name."); + } + else if (!strcmp(argv[i], "-e")) { + if (i+1 < argc) { + optionExceptionFile.push_back(argv[++i]); + } + else usage(progName, "-e argument requires a file name."); + } else usage(progName, (((SWBuf)"Unknown argument: ")+ argv[i]).c_str()); } @@ -72,16 +99,23 @@ int main(int argc, char **argv) { exit(1); } SWModule &targetMod = *m; - m = lib.getModule(strongsSourceModuleName); + m = lib.getModule(strongsSourceModuleName.c_str()); if (!m) { - cerr << "couldn't find source module: " << strongsSourceModuleName << ".\n"; + cerr << "couldn't find source module: " << strongsSourceModuleName.c_str() << ".\n"; exit(1); } SWModule &fromMod = *m; + for (int i = 0; i < optionExceptionFile.size(); ++i) { + SWBuf fileName = optionExceptionFile[i]; + if (!i) exceptionFile = new SWConfig(fileName); + else (*exceptionFile) += SWConfig(fileName); + } + // we'll do the whole Bible eventually, but let's just get one verse // working well. - targetMod.setKey("mat1.1"); // let's try this verse + ((VerseKey *)targetMod.getKey())->setIntros(true); + targetMod.getKey()->setText("mat0.0"); // let's try this verse int z = 0; for (; //!z && @@ -147,7 +181,7 @@ if (optionDebug) { cout << "\nProcessing Verse: " << targetMod.getKeyText() << endl; cout << "---------------------" << endl; - cout << "\nOur FromMod Verse Markup" << endl; + cout << "\nOur strongsSourceModule Markup" << endl; cout << "---------------------" << endl; cout << fromMod.getRawEntry() << endl; cout << "---------------------" << endl; @@ -157,18 +191,20 @@ if (optionDebug) { // grab our raw, fully marked up TargetMod text for this verse SWBuf orig = targetMod.getRawEntryBuf(); - if (optionFilterAccents) { - UTF8GreekAccents filter; - filter.setOptionValue("off"); - filter.processText(orig); - } + +if (optionDebug) { + cout << "\nOur Original targetModule Markup" << endl; + cout << "---------------------" << endl; + cout << orig << endl; + cout << "---------------------" << endl; +} if (optionFilterAppCrit) { SWBuf o = orig; const unsigned char* from = (unsigned char*)o.c_str(); orig = ""; while (*from) { - __u32 ch = getUniCharFromUTF8(&from, true); + SW_u32 ch = getUniCharFromUTF8(&from, true); // if ch is bad, then convert to replacement char if (!ch) ch = 0xFFFD; SWBuf checkChar; @@ -178,13 +214,6 @@ if (optionDebug) { } } -if (optionDebug) { - cout << "\nOur Original TargetMod Markup" << endl; - cout << "---------------------" << endl; - cout << orig << endl; - cout << "---------------------" << endl; -} - // let's find where just the canonical text is amongst // all our markup // newTargetModMarkup will eventually hold our updated markup with @@ -196,7 +225,7 @@ if (optionDebug) { SWBuf newTargetModMarkup = findCanonicalBibleText(orig, bibMap, wTags); if (optionDebug) { - cout << "\nOur Original TargetMod Markup After XMLTag-ifying" << endl; + cout << "\nOur Original targetModule Markup After XMLTag-ifying" << endl; cout << "---------------------" << endl; cout << newTargetModMarkup << endl; cout << "---------------------" << endl; @@ -214,7 +243,7 @@ if (optionDebug) { justTargetModBibleText = buildWordMaps(newTargetModMarkup, bibMap, targetWords, targetWordStarts, targetWordEnds); if (optionDebug) { - cout << "\nJust TargetMod Bible Text" << endl; + cout << "\nJust targetModule Bible Text" << endl; cout << "---------------------" << endl; cout << justTargetModBibleText << endl; cout << "---------------------" << endl; @@ -234,14 +263,13 @@ if (optionDebug) { // matcher->matchWords(targetWordTags, targetWords, fromWords, fromWordTags); - // ok, now that we have our targetWordTags magically populated // let's do the grunt work of inserting the and tags - insertWordTags(newTargetModMarkup, bibMap, wTags, targetWordTags, wordTags, targetWordStarts, targetWordEnds); + insertWordTags((VerseKey *)targetMod.getKey(), newTargetModMarkup, bibMap, wTags, targetWordTags, wordTags, targetWordStarts, targetWordEnds); if (optionDebug) { - cout << "\nHere's how you mapped things..." << endl; + cout << "\nHere's how we mapped things..." << endl; cout << "---------------------" << endl; cout << "Total wordTags: " << wordTags.size() << endl; cout << "\nTargetMod Words: " << endl; @@ -251,7 +279,7 @@ if (optionDebug) { if (targetWordTags[i] == -1 && !strstr(ignoreSeries, targetWords[i])) { if (!warned) { cerr << "*** Error: didn't match all words: " << targetMod.getKeyText() << endl; - cerr << strongsSourceModuleName << ":"; + cerr << strongsSourceModuleName.c_str() << ":"; for (int j = 0; j < fromWords.size(); ++j) { cerr << " " << fromWords[j]; } @@ -268,7 +296,7 @@ if (optionDebug) { cerr << " " << i << ": " << targetWords[i] << " (" << matcher->sanitizeWord(targetWords[i]) << ")" << endl; } if (optionDebug) { - cout << targetWords[i] << " : " << targetWordTags[i] << " => " << (targetWordTags[i] != -1 ? wordTags[targetWordTags[i]] : "") << endl; + cout << targetWords[i] << " : " << targetWordTags[i] << " => " << (targetWordTags[i] > -1 ? wordTags[targetWordTags[i]] : "") << endl; } } if (warned) { @@ -276,15 +304,22 @@ if (optionDebug) { VerseKey *vk = (VerseKey *)targetMod.getKey(); for (int j = 0; j < targetWords.size(); ++j) { if (!strstr(ignoreSeries, targetWords[j])) { - cerr << targetWords[j] << "\t\t " << vk->getOSISRef() << "." << j << "=" << (targetWordTags[j] != -1 ? wordTags[targetWordTags[j]] : "") << endl; + cerr << targetWords[j] << "\t\t " << vk->getOSISRef() << "." << j << "=" << (targetWordTags[j] > -1 ? (const char *)wordTags[targetWordTags[j]] : (targetWordTags[j] == -2 ? "{Using Exception}" : "")) << endl; } } cerr << "---------------------" << endl; } + + if (optionFilterAccents) { + UTF8GreekAccents filter; + filter.setOptionValue("off"); + filter.processText(newTargetModMarkup); + } + if (optionDebug) { cout << "---------------------" << endl; - cout << "\nAND... Here's your final output" << endl; + cout << "\nAND... Here's our final output" << endl; cout << "---------------------" << endl; } cout << newTargetModMarkup << endl; @@ -292,6 +327,9 @@ if (optionDebug) { cout << endl; } } + + delete exceptionFile; + return 0; } @@ -305,6 +343,8 @@ SWBuf findCanonicalBibleText(SWBuf orig, BibMap &bibMap, BibMap &wTags) { int tagLevel = 0; int wTag = -1; int inTag = 0; + bool wTagsPresent = orig.indexOf(" -1; + SWBuf lastElementText = ""; for (int i = 0; i < orig.length(); ++i) { if (orig[i] == '<') { inTag = true; @@ -312,30 +352,48 @@ SWBuf findCanonicalBibleText(SWBuf orig, BibMap &bibMap, BibMap &wTags) { else if (orig[i] == '>') { inTag = false; XMLTag t = tag.c_str(); + bool skipTag = false; if (!t.isEmpty()) { if (t.isEndTag()) { + // clear out empty w tags + if (t.getName() && !strcmp("w", t.getName())) { + if (!lastElementText.size()) { + out.setSize(wTag); + if (out.endsWith(' ')) { // && i < (orig.length() - 1) && orig[i+1] == ' ') { + out.setSize(out.size() - 1); + bibMap.pop_back(); + wTags.pop_back(); + } + skipTag = true; + } + } tagLevel--; wTag = -1; } else { + lastElementText = ""; tagLevel++; wTag = (t.getName() && !strcmp("w", t.getName())) ? out.size() : -1; } } - out += t; + if (!skipTag) out += t; tag = ""; } else if (inTag) { tag += orig[i]; } else { -// for texts without tags -// if (!tagLevel || wTag != -1) { - if (wTag != -1 || orig[i] == ' ') { + if ( + // for texts without tags + (!wTagsPresent && (!tagLevel || wTag != -1)) + // for texts with tags + || ( wTagsPresent && (wTag != -1 || orig[i] == ' ')) + ) { bibMap.push_back(out.size()); wTags.push_back(wTag); } out += orig[i]; + lastElementText += orig[i]; } } return out; @@ -480,13 +538,32 @@ void pullFromModData(SWModule &fromMod, vector&wordTags, vector & } -void insertWordTags(SWBuf &markupBuf, BibMap &bibMap, BibMap &wTags, const vector &targetWordTags, const vector &wordTags, const vector &targetWordStarts, const vector &targetWordEnds) { +void insertWordTags(VerseKey *vk, SWBuf &markupBuf, BibMap &bibMap, BibMap &wTags, vector &targetWordTags, const vector &wordTags, const vector &targetWordStarts, const vector &targetWordEnds) { // TODO: this method needs some work, // like putting multiple consecutive words // together in one tag + + ConfigEntMap exceptions; + + if (exceptionFile) { + exceptions = exceptionFile->getSection("exceptions"); + } + for (int i = 0; i < targetWordTags.size(); ++i) { + SWBuf wordTag = ""; if (targetWordTags[i] > -1) { - insert((const char *)wordTags[targetWordTags[i]], markupBuf, targetWordStarts[i], bibMap, wTags); + wordTag = wordTags[targetWordTags[i]]; + } + if (exceptionFile) { + SWBuf key; key.setFormatted("%s.%d", vk->getOSISRef(), i); + ConfigEntMap::const_iterator it = exceptions.find(key); + if (it != exceptions.end()) { + targetWordTags[i] = -2; // note that we are using an exception, not a mapping, not unset (-1) + wordTag = it->second; + } + } + if (wordTag.length()) { + insert((const char *)wordTag, markupBuf, targetWordStarts[i], bibMap, wTags); insert("", markupBuf, targetWordEnds[i], bibMap, wTags, true); } } diff --git a/migratetags/tag-exceptions-na28-example.conf b/migratetags/tag-exceptions-na28-example.conf new file mode 100644 index 0000000..c9b6f9f --- /dev/null +++ b/migratetags/tag-exceptions-na28-example.conf @@ -0,0 +1,365 @@ +[exceptions] +Matt.0= +Matt.1= +Matt.6.24.3= +Matt.9.4.9= +Matt.12.47.16= +Matt.15.6.1= +Matt.15.6.2= +Matt.15.6.3= +Matt.15.6.5= +Matt.15.6.6= +Matt.15.6.10= +Matt.26.60.8= +Matt.26.60.9= +Matt.26.60.10= +Matt.26.60.11= +Matt.27.46.22= +Mark.0= +Mark.1= +Mark.3.20.1= +Mark.3.20.2= +Mark.3.20.3= +Mark.3.20.4= +Mark.6.27.11= +Mark.6.27.12= +Mark.6.27.13= +Mark.6.27.14= +Mark.6.27.15= +Mark.6.27.16= +Mark.6.27.17= +Mark.12.14.37= +Mark.12.14.38= +Mark.12.14.39= +Mark.12.14.40= +Mark.13.30.10= +Mark.16.8.20= +Mark.16.8.21= +Mark.16.8.22= +Mark.16.8.23= +Mark.16.8.24= +Mark.16.8.25= +Mark.16.8.26= +Mark.16.8.27= +Mark.16.8.28= +Mark.16.8.29= +Mark.16.8.30= +Mark.16.8.31= +Mark.16.8.32= +Mark.16.8.33= +Mark.16.8.34= +Mark.16.8.35= +Mark.16.8.36= +Mark.16.8.38= +Mark.16.8.39= +Mark.16.8.40= +Mark.16.8.41= +Mark.16.8.42= +Mark.16.8.43= +Mark.16.8.44= +Mark.16.8.46= +Mark.16.8.47= +Mark.16.8.48= +Mark.16.8.49= +Mark.16.8.50= +Mark.16.8.51= +Mark.16.8.52= +Mark.16.8.53= +Mark.16.8.54= +Mark.16.8.55= +Luke.0= +Luke.1= +Luke.6.18.1= +Luke.6.18.2= +Luke.6.18.3= +Luke.6.18.5= +Luke.6.18.7= +Luke.6.18.8= +Luke.6.18.9= +Luke.6.18.10= +Luke.6.18.11= +Luke.6.18.13= +Luke.7.18.9= +Luke.7.18.10= +Luke.7.18.11= +Luke.7.18.12= +Luke.7.18.13= +Luke.7.18.14= +Luke.7.18.15= +Luke.7.18.16= +Luke.7.18.17= +Luke.13.7.25= +Luke.17.23.5= +Luke.22.67.0= +John.0= +John.1= +John.18.2.0= +Acts.0= +Acts.1= +Acts.2.11.0= +Acts.2.11.1= +Acts.2.11.3= +Acts.2.11.5= +Acts.3.20.0= +Acts.3.20.1= +Acts.3.20.2= +Acts.3.20.3= +Acts.3.20.4= +Acts.3.20.5= +Acts.3.20.6= +Acts.3.20.7= +Acts.3.20.8= +Acts.4.25.12= +Acts.5.39.14= +Acts.5.39.15= +Acts.5.39.16= +Acts.7.26.17= +Acts.13.33.0= +Acts.13.33.1= +Acts.13.33.2= +Acts.13.33.3= +Acts.13.33.4= +Acts.13.33.5= +Acts.13.33.6= +Acts.13.33.7= +Acts.13.33.8= +Acts.13.33.9= +Acts.13.33.10= +Acts.13.38.14= +Acts.13.38.15= +Acts.13.38.16= +Acts.13.38.17= +Acts.13.38.18= +Acts.13.38.19= +Acts.13.38.20= +Acts.13.38.21= +Acts.13.38.22= +Acts.13.38.23= +Acts.19.40.21= +Acts.19.40.22= +Acts.19.40.23= +Acts.19.40.24= +Acts.19.40.25= +Acts.19.40.26= +Acts.24.19.0= +Acts.24.19.1= +Acts.24.19.2= +Acts.24.19.3= +Acts.24.19.4= +Acts.24.19.5= +Rom.0= +Rom.1= +Rom.7.10.0= +Rom.7.10.1= +Rom.7.10.2= +Rom.9.12.0= +Rom.9.12.1= +Rom.9.12.2= +Rom.9.12.3= +Rom.9.12.5= +Rom.9.12.6= +Rom.9.12.7= +1Cor.0= +1Cor.1= +1Cor.2= +1Cor.10.29.10= +2Cor.0= +2Cor.1= +2Cor.2= +2Cor.1.7.1= +2Cor.1.7.2= +2Cor.1.7.3= +2Cor.1.7.4= +2Cor.1.7.5= +2Cor.1.7.6= +2Cor.1.7.15= +2Cor.8.14.0= +2Cor.8.14.1= +2Cor.8.14.2= +2Cor.8.14.3= +2Cor.8.14.13= +2Cor.8.14.14= +2Cor.8.14.15= +2Cor.8.14.17= +2Cor.8.14.18= +2Cor.8.14.19= +2Cor.8.14.20= +2Cor.10.4.15= +2Cor.10.4.16= +2Cor.13.12.5= +2Cor.13.12.6= +2Cor.13.12.7= +2Cor.13.12.8= +2Cor.13.12.9= +2Cor.13.13.0= +2Cor.13.13.1= +2Cor.13.13.2= +2Cor.13.13.3= +2Cor.13.13.4= +2Cor.13.13.5= +2Cor.13.13.6= +2Cor.13.13.7= +2Cor.13.13.8= +2Cor.13.13.9= +2Cor.13.13.10= +2Cor.13.13.11= +2Cor.13.13.12= +2Cor.13.13.13= +2Cor.13.13.14= +2Cor.13.13.15= +2Cor.13.13.16= +2Cor.13.13.17= +2Cor.13.13.18= +2Cor.13.13.19= +Gal.0= +Gal.1= +Gal.2.19.9= +Gal.2.19.10= +Gal.3.19.8= +Gal.4.19.5= +Eph.0= +Eph.1= +Eph.1.10.21= +Eph.1.10.22= +Eph.2.14.17= +Eph.2.14.18= +Eph.2.14.19= +Eph.2.14.20= +Eph.2.14.21= +Eph.2.14.22= +Eph.3.17.10= +Eph.3.17.11= +Eph.3.17.12= +Eph.3.17.13= +Eph.3.17.14= +Eph.5.14.0= +Eph.5.14.1= +Eph.5.14.2= +Eph.5.14.3= +Eph.5.14.4= +Eph.5.14.5= +Phil.0= +Phil.1= +Phil.3.7.0= +Col.0= +Col.1= +Col.1.22.0= +Col.1.22.1= +Col.1.22.2= +1Thess.0= +1Thess.1= +1Thess.2= +1Thess.1.2.14= +1Thess.2.7.0= +1Thess.2.7.2= +1Thess.2.7.3= +1Thess.2.7.5= +1Thess.2.7.6= +1Thess.2.7.11= +1Thess.2.7.14= +1Thess.2.12.0= +1Thess.2.12.3= +1Thess.2.12.15= +1Thess.2.12.20= +2Thess.0= +2Thess.1= +2Thess.2= +1Tim.0= +1Tim.1= +1Tim.2= +2Tim.0= +2Tim.1= +2Tim.2= +Titus.0= +Titus.1= +Phlm.0= +Phlm.1= +Heb.0= +Heb.1= +Heb.1.2.0= +Heb.1.2.2= +Heb.1.2.3= +Heb.1.2.4= +Heb.1.2.5= +Heb.1.2.6= +Heb.1.2.7= +Heb.1.2.8= +Heb.1.2.9= +Heb.3.10.0= +Heb.3.10.1= +Heb.3.13.7= +Heb.12.4.2= +Heb.12.22.13= +Jas.0= +Jas.1= +Jas.1.20.5= +Jas.1.20.6= +Jas.2.4.5= +Jas.2.15.9= +Jas.4.10.3= +Jas.5.10.4= +1Pet.0= +1Pet.1= +1Pet.2= +1Pet.1.6.8= +1Pet.3.16.0= +1Pet.3.16.1= +1Pet.3.16.2= +1Pet.3.16.3= +1Pet.3.16.4= +1Pet.4.16.12= +1Pet.5.1.1= +2Pet.0= +2Pet.1= +2Pet.2= +2Pet.2.6.10= +2Pet.2.11.13= +2Pet.2.15.0= +2Pet.2.18.10= +2Pet.3.6.2= +2Pet.3.10.25= +2Pet.3.16.4= +2Pet.3.16.21= +1John.0= +1John.1= +1John.2= +1John.2.14.2= +1John.2.14.6= +1John.2.14.11= +1John.2.14.16= +1John.2.14.19= +1John.2.14.28= +1John.2.14.32= +1John.3.7.0= +1John.5.10.12= +1John.5.18.18= +2John.0= +2John.1= +2John.2= +3John.0= +3John.1= +3John.2= +Jude.0= +Jude.1= +Jude.1.5.10= +Rev.0= +Rev.1= +Rev.2.25.4= +Rev.2.28.0= +Rev.2.28.1= +Rev.2.28.2= +Rev.2.28.3= +Rev.2.28.4= +Rev.2.28.5= +Rev.2.28.6= +Rev.12.17.28= +Rev.12.17.29= +Rev.12.17.30= +Rev.12.17.31= +Rev.12.17.32= +Rev.12.17.33= +Rev.12.17.34= +Rev.17.9.19= +Rev.17.9.20= +Rev.17.9.21= +Rev.17.9.22= -- cgit