From 8859b5c84c7216e32747d6c7e0724108251b7f88 Mon Sep 17 00:00:00 2001 From: Marcos Del Sol Vives Date: Fri, 26 Sep 2025 14:07:34 +0200 Subject: [PATCH 1/2] Improve version sorting A new function has been added that properly handles semantic and dated versions. Versioned entries also now inherit the date from the last version. --- lib/bibref.js | 57 ++++++++++++++++++++++++++++++++++------ test/bibref.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 8 deletions(-) diff --git a/lib/bibref.js b/lib/bibref.js index 58ed4ae8c..9064ebf73 100644 --- a/lib/bibref.js +++ b/lib/bibref.js @@ -95,7 +95,7 @@ function addRefs(refs) { Bibref.prototype.doneAddingRefs = doneAddingRefs; function doneAddingRefs() { - this.all = cleanupRefs(addIds(createUppercaseRefs(expandRefs(this.raw)))); + this.all = cleanupRefs(flattenVersions(addIds(createUppercaseRefs(expandRefs(this.raw))))); this.reverseLookupTable = prepareReverseLookup(this.all); } @@ -221,6 +221,53 @@ function createUppercaseRefs(refs) { return refs; } +function versionComparator(a, b) { + // Find consecutive letters or numbers (but not both mixed) + const regex = /[0-9]+|[a-zA-Z]+/g; + const partsA = a.match(regex); + const partsB = b.match(regex); + + const len = Math.min(partsA.length, partsB.length); + for (let i = 0; i < len; i++) { + const compA = partsA[i]; + const compB = partsB[i]; + + const numA = parseInt(compA, 10); + const numB = parseInt(compB, 10); + + // If any is non-numeric, compare as string. Otherwise compare numerically. + if (isNaN(numA) || isNaN(numB)) { + if (compA < compB) return -1; + if (compA > compB) return 1; + } else { + if (numA < numB) return -1; + if (numA > numB) return 1; + } + } + + // All matched elements are equal, so shorter array is smaller + return partsA.length - partsB.length; +} + +function flattenVersions(refs) { + for (const [refId, ref] of Object.entries(refs)) { + if (ref.versions) { + // Sort versions, with newest at 0 and oldest at n-1 + const sortedVersions = Object.keys(ref.versions).sort(versionComparator); + sortedVersions.reverse(); + + // If main object does not have a raw date but the latest version does, copy it + if (!ref.rawDate && sortedVersions.length > 0 && ref.versions[sortedVersions[0]].rawDate) { + ref.rawDate = ref.versions[sortedVersions[0]].rawDate; + } + + // Now convert to string array + ref.versions = sortedVersions.map((versionId) => refId + "-" + versionId); + } + } + return refs; +} + function cleanupRefs(refs) { Object.keys(refs).forEach(function(k) { var ref = refs[k]; @@ -235,13 +282,6 @@ function cleanupRefs(refs) { return { url: url, shortname: _getWGShortNameFromURL(url) }; }); } - if (prop == "versions") { - ref.versions = Object.keys(ref.versions).map(function(subKey) { - return k + "-" + subKey; - }); - ref.versions.sort(); - ref.versions.reverse(); - } } } }); @@ -335,6 +375,7 @@ module.exports.create = function() { }; module.exports.expandRefs = expandRefs; module.exports.createUppercaseRefs = createUppercaseRefs; +module.exports.flattenVersions = flattenVersions; module.exports.cleanupRefs = cleanupRefs; module.exports.findLatest = findLatest; module.exports.normalizeUrl = normalizeUrl; diff --git a/test/bibref.js b/test/bibref.js index 0de07aa22..17d53597f 100644 --- a/test/bibref.js +++ b/test/bibref.js @@ -99,7 +99,77 @@ suite('Test bibref api', function() { } }; assert.equal("2010-01-01", bibref.findLatest(complex).rawDate); + }); + test('bibref.flattenVersions handles date versions properly', function() { + var cleaned = bibref.flattenVersions({ + foo: { + versions: { + '2024-04-05': { + href: 'http://example.com/2', + rawDate: '2024-04-05' + }, + '2025-10-05': { + href: 'http://example.com/3', + rawDate: '2025-10-05' + }, + '2004-12-11': { + href: 'http://example.com/1', + rawDate: '2004-12-11' + }, + '2030-01-05': { + href: 'http://example.com/4', + rawDate: '2030-01-05' + } + } + } + }); + var foo = cleaned.foo; + assert.equal(typeof foo, 'object', '"foo" is an object'); + assert.equal(foo.rawDate, '2030-01-05', 'Raw date is that of the last version'); + assert.equal(foo.versions.length, 4, 'Versions array has 4 entries'); + assert.equal(foo.versions[0], "foo-2030-01-05", 'First element is the newest version'); + assert.equal(foo.versions[1], "foo-2025-10-05", 'Second element is the previous version'); + assert.equal(foo.versions[2], "foo-2024-04-05", 'Third element is the second previous version'); + assert.equal(foo.versions[3], "foo-2004-12-11", 'Last element is the oldest version'); + }); + + test('bibref.flattenVersions handles semantic versions properly', function() { + var cleaned = bibref.flattenVersions({ + foo: { + versions: { + '2.1': { + href: 'http://example.com/2', + rawDate: '2004-04-05' + }, + '11.3b': { + href: 'http://example.com/5', + rawDate: '2030-01-06' + }, + '2': { + href: 'http://example.com/1', + rawDate: '2004-12-11' + }, + '11.3': { + href: 'http://example.com/3', + rawDate: '2030-01-04' + }, + '11.3a': { + href: 'http://example.com/4', + rawDate: '2030-01-05' + } + } + } + }); + var foo = cleaned.foo; + assert.equal(typeof foo, 'object', '"foo" is an object'); + assert.equal(foo.rawDate, '2030-01-06', 'Raw date is that of the last version'); + assert.equal(foo.versions.length, 5, 'Versions array has 5 entries'); + assert.equal(foo.versions[0], "foo-11.3b", 'First element is the newest version'); + assert.equal(foo.versions[1], "foo-11.3a", 'Second element is the previous version'); + assert.equal(foo.versions[2], "foo-11.3", 'Third element is the second previous version'); + assert.equal(foo.versions[3], "foo-2.1", 'Fourth element is an old version'); + assert.equal(foo.versions[4], "foo-2", 'Last element is an ancient version'); }); test('bibref.get returns the proper ref', function() { From f5aabf917a9252153969fc1569af4c099d61e3d5 Mon Sep 17 00:00:00 2001 From: Marcos Del Sol Vives Date: Fri, 26 Sep 2025 14:10:46 +0200 Subject: [PATCH 2/2] Remove unused function This function was not used, and now the latest version always comes first in the version array. --- lib/bibref.js | 10 ---------- test/bibref.js | 20 -------------------- 2 files changed, 30 deletions(-) diff --git a/lib/bibref.js b/lib/bibref.js index 9064ebf73..4521386cc 100644 --- a/lib/bibref.js +++ b/lib/bibref.js @@ -300,15 +300,6 @@ function addIds(refs) { var EIGHT_DIGITS = /^\d{8}$/; -function findLatest(ref) { - var keys, latest = null; - if (ref.versions) { - keys = Object.keys(ref.versions).filter(function(k) { return EIGHT_DIGITS.test(k); }).sort(); - latest = ref.versions[keys[keys.length - 1]]; - } - return latest; -} - Bibref.prototype.get = get; function get(ref, output) { output = output || {}; @@ -377,6 +368,5 @@ module.exports.expandRefs = expandRefs; module.exports.createUppercaseRefs = createUppercaseRefs; module.exports.flattenVersions = flattenVersions; module.exports.cleanupRefs = cleanupRefs; -module.exports.findLatest = findLatest; module.exports.normalizeUrl = normalizeUrl; diff --git a/test/bibref.js b/test/bibref.js index 17d53597f..f02800c9d 100644 --- a/test/bibref.js +++ b/test/bibref.js @@ -81,26 +81,6 @@ suite('Test bibref api', function() { assert.equal(foo.deliveredBy[0].shortname, 'html', "The url http://www.w3.org/html/wg/ gets properly turned into the html shortname."); }); - test('bibref.findLatest finds the latest version of the ref', function() { - var basic = { - versions: { - "20091010": { rawDate: "2009-10-10" }, - "20100101": { rawDate: "2010-01-01" } - } - }; - assert.equal("2010-01-01", bibref.findLatest(basic).rawDate); - - var complex = { - versions: { - "20091010": { rawDate: "2009-10-10" }, - "20100101": { rawDate: "2010-01-01" }, - "2e": { rawDate: "1998-02-02" }, - "999999999948393": { rawDate: "1997-02-02" } - } - }; - assert.equal("2010-01-01", bibref.findLatest(complex).rawDate); - }); - test('bibref.flattenVersions handles date versions properly', function() { var cleaned = bibref.flattenVersions({ foo: {