@cyanheads/pubmed-mcp-server 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +174 -0
- package/dist/config/index.d.ts +91 -0
- package/dist/config/index.js +324 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +190 -0
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.d.ts +79 -0
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.js +82 -0
- package/dist/mcp-server/resources/echoResource/index.d.ts +13 -0
- package/dist/mcp-server/resources/echoResource/index.js +13 -0
- package/dist/mcp-server/resources/echoResource/registration.d.ts +30 -0
- package/dist/mcp-server/resources/echoResource/registration.js +168 -0
- package/dist/mcp-server/server.d.ts +28 -0
- package/dist/mcp-server/server.js +159 -0
- package/dist/mcp-server/tools/fetchPubMedContent/index.d.ts +6 -0
- package/dist/mcp-server/tools/fetchPubMedContent/index.js +6 -0
- package/dist/mcp-server/tools/fetchPubMedContent/logic.d.ts +63 -0
- package/dist/mcp-server/tools/fetchPubMedContent/logic.js +518 -0
- package/dist/mcp-server/tools/fetchPubMedContent/registration.d.ts +10 -0
- package/dist/mcp-server/tools/fetchPubMedContent/registration.js +37 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/index.d.ts +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/index.js +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.d.ts +9 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +288 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.d.ts +9 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.js +208 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.d.ts +15 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.js +82 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.d.ts +27 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.js +5 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic.d.ts +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic.js +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.d.ts +36 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.js +76 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/index.d.ts +6 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/index.js +6 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/index.d.ts +7 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/index.js +9 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/inputSchema.d.ts +111 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/inputSchema.js +149 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/outputTypes.d.ts +106 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/outputTypes.js +7 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.d.ts +10 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.js +251 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.d.ts +12 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.js +55 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.d.ts +10 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.js +36 -0
- package/dist/mcp-server/tools/searchPubMedArticles/index.d.ts +6 -0
- package/dist/mcp-server/tools/searchPubMedArticles/index.js +6 -0
- package/dist/mcp-server/tools/searchPubMedArticles/logic.d.ts +61 -0
- package/dist/mcp-server/tools/searchPubMedArticles/logic.js +229 -0
- package/dist/mcp-server/tools/searchPubMedArticles/registration.d.ts +10 -0
- package/dist/mcp-server/tools/searchPubMedArticles/registration.js +38 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +29 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.js +174 -0
- package/dist/mcp-server/transports/httpTransport.d.ts +23 -0
- package/dist/mcp-server/transports/httpTransport.js +463 -0
- package/dist/mcp-server/transports/stdioTransport.d.ts +42 -0
- package/dist/mcp-server/transports/stdioTransport.js +63 -0
- package/dist/services/NCBI/ncbiConstants.d.ts +22 -0
- package/dist/services/NCBI/ncbiConstants.js +5 -0
- package/dist/services/NCBI/ncbiCoreApiClient.d.ts +24 -0
- package/dist/services/NCBI/ncbiCoreApiClient.js +118 -0
- package/dist/services/NCBI/ncbiRequestQueueManager.d.ts +36 -0
- package/dist/services/NCBI/ncbiRequestQueueManager.js +96 -0
- package/dist/services/NCBI/ncbiResponseHandler.d.ts +22 -0
- package/dist/services/NCBI/ncbiResponseHandler.js +192 -0
- package/dist/services/NCBI/ncbiService.d.ts +24 -0
- package/dist/services/NCBI/ncbiService.js +57 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.js +7 -0
- package/dist/services/llm-providers/index.d.ts +7 -0
- package/dist/services/llm-providers/index.js +7 -0
- package/dist/services/llm-providers/llmFactory.d.ts +69 -0
- package/dist/services/llm-providers/llmFactory.js +132 -0
- package/dist/services/llm-providers/openRouter/index.d.ts +6 -0
- package/dist/services/llm-providers/openRouter/index.js +7 -0
- package/dist/services/llm-providers/openRouter/openRouterProvider.d.ts +99 -0
- package/dist/services/llm-providers/openRouter/openRouterProvider.js +329 -0
- package/dist/types-global/errors.d.ts +119 -0
- package/dist/types-global/errors.js +119 -0
- package/dist/types-global/pubmedXml.d.ts +350 -0
- package/dist/types-global/pubmedXml.js +7 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/internal/errorHandler.d.ts +176 -0
- package/dist/utils/internal/errorHandler.js +335 -0
- package/dist/utils/internal/index.d.ts +9 -0
- package/dist/utils/internal/index.js +9 -0
- package/dist/utils/internal/logger.d.ts +141 -0
- package/dist/utils/internal/logger.js +426 -0
- package/dist/utils/internal/requestContext.d.ts +83 -0
- package/dist/utils/internal/requestContext.js +72 -0
- package/dist/utils/metrics/index.d.ts +7 -0
- package/dist/utils/metrics/index.js +7 -0
- package/dist/utils/metrics/tokenCounter.d.ts +35 -0
- package/dist/utils/metrics/tokenCounter.js +110 -0
- package/dist/utils/parsing/dateParser.d.ts +73 -0
- package/dist/utils/parsing/dateParser.js +107 -0
- package/dist/utils/parsing/index.d.ts +8 -0
- package/dist/utils/parsing/index.js +8 -0
- package/dist/utils/parsing/jsonParser.d.ts +82 -0
- package/dist/utils/parsing/jsonParser.js +126 -0
- package/dist/utils/parsing/ncbi-parsing/eSummaryResultParser.d.ts +32 -0
- package/dist/utils/parsing/ncbi-parsing/eSummaryResultParser.js +295 -0
- package/dist/utils/parsing/ncbi-parsing/index.d.ts +8 -0
- package/dist/utils/parsing/ncbi-parsing/index.js +8 -0
- package/dist/utils/parsing/ncbi-parsing/pubmedArticleStructureParser.d.ts +70 -0
- package/dist/utils/parsing/ncbi-parsing/pubmedArticleStructureParser.js +229 -0
- package/dist/utils/parsing/ncbi-parsing/xmlGenericHelpers.d.ts +31 -0
- package/dist/utils/parsing/ncbi-parsing/xmlGenericHelpers.js +76 -0
- package/dist/utils/security/idGenerator.d.ts +134 -0
- package/dist/utils/security/idGenerator.js +198 -0
- package/dist/utils/security/index.d.ts +9 -0
- package/dist/utils/security/index.js +9 -0
- package/dist/utils/security/rateLimiter.d.ts +108 -0
- package/dist/utils/security/rateLimiter.js +179 -0
- package/dist/utils/security/sanitization.d.ts +173 -0
- package/dist/utils/security/sanitization.js +439 -0
- package/package.json +97 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Helper functions for parsing ESummary results from NCBI.
|
|
3
|
+
* Handles different ESummary XML structures and formats the data into
|
|
4
|
+
* consistent ParsedBriefSummary objects.
|
|
5
|
+
* @module src/utils/parsing/ncbi-parsing/eSummaryResultParser
|
|
6
|
+
*/
|
|
7
|
+
import { dateParser, logger, requestContextService, } from "../../../utils/index.js"; // Note: utils/index.js is the barrel file
|
|
8
|
+
import { ensureArray, getAttribute, getText } from "./xmlGenericHelpers.js";
|
|
9
|
+
/**
|
|
10
|
+
* Formats an array of ESummary authors into a string.
|
|
11
|
+
* Limits to the first 3 authors and adds "et al." if more exist.
|
|
12
|
+
* @param authors - Array of ESummary author objects (normalized).
|
|
13
|
+
* @returns A string like "Doe J, Smith A, Brown B, et al." or empty if no authors.
|
|
14
|
+
*/
|
|
15
|
+
export function formatESummaryAuthors(authors) {
|
|
16
|
+
if (!authors || authors.length === 0)
|
|
17
|
+
return "";
|
|
18
|
+
return (authors
|
|
19
|
+
.slice(0, 3)
|
|
20
|
+
.map((author) => author.name) // Assumes author.name is the string representation
|
|
21
|
+
.join(", ") + (authors.length > 3 ? ", et al." : ""));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Standardizes date strings from ESummary to "YYYY-MM-DD" format.
|
|
25
|
+
* Uses the dateParser utility.
|
|
26
|
+
* @param dateStr - Date string from ESummary (e.g., "2023/01/15", "2023 Jan 15", "2023").
|
|
27
|
+
* @param parentContext - Optional parent request context for logging.
|
|
28
|
+
* @returns A promise resolving to a standardized date string ("YYYY-MM-DD") or undefined if parsing fails.
|
|
29
|
+
*/
|
|
30
|
+
export async function standardizeESummaryDate(dateStr, parentContext) {
|
|
31
|
+
if (dateStr === undefined || dateStr === null)
|
|
32
|
+
return undefined; // Check for null as well
|
|
33
|
+
const dateInputString = String(dateStr); // Ensure it's a string
|
|
34
|
+
const currentContext = parentContext ||
|
|
35
|
+
requestContextService.createRequestContext({
|
|
36
|
+
operation: "standardizeESummaryDateInternal",
|
|
37
|
+
inputDate: dateInputString, // Log the stringified version
|
|
38
|
+
});
|
|
39
|
+
try {
|
|
40
|
+
// Pass the stringified version to the date parser
|
|
41
|
+
const parsedDate = await dateParser.parseDate(dateInputString, currentContext);
|
|
42
|
+
if (parsedDate) {
|
|
43
|
+
return parsedDate.toISOString().split("T")[0]; // Format as YYYY-MM-DD
|
|
44
|
+
}
|
|
45
|
+
logger.debug(`standardizeESummaryDate: dateParser could not parse "${dateInputString}", returning undefined.`, currentContext);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
logger.warning(`standardizeESummaryDate: Error during dateParser.parseDate for "${dateInputString}", returning undefined.`, {
|
|
49
|
+
...currentContext,
|
|
50
|
+
error: e instanceof Error ? e.message : String(e),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return undefined; // Return undefined if parsing fails
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parses authors from an ESummary DocumentSummary structure.
|
|
57
|
+
* Handles various ways authors might be represented.
|
|
58
|
+
* Returns an array of normalized XmlESummaryAuthor objects.
|
|
59
|
+
* Internal helper function.
|
|
60
|
+
*/
|
|
61
|
+
function parseESummaryAuthorsFromDocumentSummary(docSummary) {
|
|
62
|
+
const authorsProp = docSummary.Authors;
|
|
63
|
+
if (!authorsProp)
|
|
64
|
+
return [];
|
|
65
|
+
const parsedAuthors = [];
|
|
66
|
+
const processRawAuthor = (rawAuthInput) => {
|
|
67
|
+
let name = "";
|
|
68
|
+
let authtype;
|
|
69
|
+
let clusterid;
|
|
70
|
+
if (typeof rawAuthInput === "string") {
|
|
71
|
+
name = rawAuthInput;
|
|
72
|
+
}
|
|
73
|
+
else if (rawAuthInput && typeof rawAuthInput === "object") {
|
|
74
|
+
const authorObj = rawAuthInput; // Now typed
|
|
75
|
+
// Try extracting text from the object itself (e.g., if it's { '#text': 'Author Name' })
|
|
76
|
+
name = getText(authorObj, "");
|
|
77
|
+
// If name is still empty, try common property names for author names
|
|
78
|
+
if (!name) {
|
|
79
|
+
name = getText(authorObj.Name || authorObj.name, "");
|
|
80
|
+
}
|
|
81
|
+
authtype = getText(authorObj.AuthType || authorObj.authtype, undefined);
|
|
82
|
+
clusterid = getText(authorObj.ClusterId || authorObj.clusterid, undefined);
|
|
83
|
+
// Fallback for unhandled structures: log and try to stringify
|
|
84
|
+
if (!name) {
|
|
85
|
+
const authInputString = JSON.stringify(authorObj);
|
|
86
|
+
logger.warning(`Unhandled author structure in parseESummaryAuthorsFromDocumentSummary. authInput: ${authInputString.substring(0, 100)}`, requestContextService.createRequestContext({
|
|
87
|
+
operation: "parseESummaryAuthorsFromDocumentSummary",
|
|
88
|
+
detail: "Unhandled author structure",
|
|
89
|
+
}));
|
|
90
|
+
// As a last resort, if it's a simple object with a single value, that might be the name
|
|
91
|
+
const keys = Object.keys(authorObj);
|
|
92
|
+
if (keys.length === 1 &&
|
|
93
|
+
typeof authorObj[keys[0]] === "string") {
|
|
94
|
+
name = authorObj[keys[0]];
|
|
95
|
+
}
|
|
96
|
+
else if (authInputString.length < 100) {
|
|
97
|
+
// Avoid overly long stringified objects
|
|
98
|
+
name = authInputString; // Not ideal, but better than empty for debugging
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (name.trim()) {
|
|
103
|
+
parsedAuthors.push({
|
|
104
|
+
name: name.trim(),
|
|
105
|
+
authtype,
|
|
106
|
+
clusterid,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
if (Array.isArray(authorsProp)) {
|
|
111
|
+
// authorsProp could be Array<string> or Array<XmlESummaryAuthorRaw>
|
|
112
|
+
authorsProp.forEach(processRawAuthor);
|
|
113
|
+
}
|
|
114
|
+
else if (typeof authorsProp === "object" &&
|
|
115
|
+
"Author" in authorsProp && // authorsProp is { Author: ... }
|
|
116
|
+
authorsProp.Author) {
|
|
117
|
+
const rawAuthors = ensureArray(authorsProp.Author);
|
|
118
|
+
rawAuthors.forEach(processRawAuthor);
|
|
119
|
+
}
|
|
120
|
+
else if (typeof authorsProp === "string") {
|
|
121
|
+
try {
|
|
122
|
+
// Attempt to parse if it looks like a JSON array string
|
|
123
|
+
if (authorsProp.startsWith("[") && authorsProp.endsWith("]")) {
|
|
124
|
+
const parsedJsonAuthors = JSON.parse(authorsProp);
|
|
125
|
+
if (Array.isArray(parsedJsonAuthors)) {
|
|
126
|
+
parsedJsonAuthors.forEach((authItem) => {
|
|
127
|
+
if (typeof authItem === "string") {
|
|
128
|
+
parsedAuthors.push({ name: authItem.trim() });
|
|
129
|
+
}
|
|
130
|
+
else if (typeof authItem === "object" &&
|
|
131
|
+
authItem !== null &&
|
|
132
|
+
(authItem.name || authItem.Name)) {
|
|
133
|
+
// If it's an object with a name property, treat as XmlESummaryAuthorRaw
|
|
134
|
+
processRawAuthor(authItem);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
if (parsedAuthors.length > 0)
|
|
138
|
+
return parsedAuthors; // Return if JSON parsing yielded results
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
logger.debug(`Failed to parse Authors string as JSON: ${authorsProp.substring(0, 100)}`, requestContextService.createRequestContext({
|
|
144
|
+
operation: "parseESummaryAuthorsFromString",
|
|
145
|
+
input: authorsProp.substring(0, 100),
|
|
146
|
+
error: e instanceof Error ? e.message : String(e),
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
// Fallback: split string by common delimiters
|
|
150
|
+
authorsProp
|
|
151
|
+
.split(/[,;]/)
|
|
152
|
+
.map((namePart) => namePart.trim())
|
|
153
|
+
.filter((namePart) => namePart)
|
|
154
|
+
.forEach((namePart) => parsedAuthors.push({ name: namePart }));
|
|
155
|
+
}
|
|
156
|
+
return parsedAuthors.filter((author) => author.name);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Parses a single ESummary DocumentSummary (newer XML format) into a raw summary object.
|
|
160
|
+
* Internal helper function.
|
|
161
|
+
*/
|
|
162
|
+
function parseSingleDocumentSummary(docSummary) {
|
|
163
|
+
const pmid = docSummary["@_uid"];
|
|
164
|
+
const authorsArray = parseESummaryAuthorsFromDocumentSummary(docSummary);
|
|
165
|
+
let doiValue = getText(docSummary.DOI, undefined);
|
|
166
|
+
if (!doiValue) {
|
|
167
|
+
const articleIdsProp = docSummary.ArticleIds;
|
|
168
|
+
if (articleIdsProp) {
|
|
169
|
+
const idsArray = Array.isArray(articleIdsProp)
|
|
170
|
+
? articleIdsProp
|
|
171
|
+
: ensureArray(articleIdsProp.ArticleId);
|
|
172
|
+
const doiEntry = idsArray.find((id) => id.idtype === "doi");
|
|
173
|
+
if (doiEntry) {
|
|
174
|
+
doiValue = getText(doiEntry.value, undefined);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
pmid: String(pmid),
|
|
180
|
+
title: getText(docSummary.Title, undefined),
|
|
181
|
+
authors: formatESummaryAuthors(authorsArray),
|
|
182
|
+
source: getText(docSummary.Source, undefined) ||
|
|
183
|
+
getText(docSummary.FullJournalName, undefined) ||
|
|
184
|
+
getText(docSummary.SO, undefined) ||
|
|
185
|
+
undefined,
|
|
186
|
+
doi: doiValue,
|
|
187
|
+
rawPubDate: getText(docSummary.PubDate, undefined),
|
|
188
|
+
rawEPubDate: getText(docSummary.EPubDate, undefined),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Parses a single ESummary DocSum (older XML item-based format) into a raw summary object.
|
|
193
|
+
* Internal helper function.
|
|
194
|
+
*/
|
|
195
|
+
function parseSingleDocSumOldXml(docSum) {
|
|
196
|
+
const pmid = docSum.Id;
|
|
197
|
+
const items = ensureArray(docSum.Item);
|
|
198
|
+
const getItemValue = (name, type) => {
|
|
199
|
+
const namesToTry = ensureArray(name);
|
|
200
|
+
for (const n of namesToTry) {
|
|
201
|
+
const item = items.find((i) => i._Name === n &&
|
|
202
|
+
(type ? i._Type === type : true) &&
|
|
203
|
+
i._Type !== "ERROR");
|
|
204
|
+
if (item) {
|
|
205
|
+
const textVal = getText(item);
|
|
206
|
+
if (textVal !== undefined)
|
|
207
|
+
return String(textVal);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
};
|
|
212
|
+
const getAuthorList = () => {
|
|
213
|
+
const authorListItem = items.find((i) => i._Name === "AuthorList" && i._Type === "List");
|
|
214
|
+
if (authorListItem && authorListItem.Item) {
|
|
215
|
+
return ensureArray(authorListItem.Item)
|
|
216
|
+
.filter((a) => a._Name === "Author" && a._Type === "String")
|
|
217
|
+
.map((a) => ({ name: getText(a, "") }));
|
|
218
|
+
}
|
|
219
|
+
// Fallback for authors directly under DocSum items
|
|
220
|
+
return items
|
|
221
|
+
.filter((i) => i._Name === "Author" && i._Type === "String")
|
|
222
|
+
.map((a) => ({ name: getText(a, "") }));
|
|
223
|
+
};
|
|
224
|
+
const authorsArray = getAuthorList();
|
|
225
|
+
let doiFromItems = getItemValue("DOI", "String");
|
|
226
|
+
if (!doiFromItems) {
|
|
227
|
+
const articleIdsItem = items.find((i) => i._Name === "ArticleIds" && i._Type === "List");
|
|
228
|
+
if (articleIdsItem && articleIdsItem.Item) {
|
|
229
|
+
const ids = ensureArray(articleIdsItem.Item);
|
|
230
|
+
const doiIdItem = ids.find((id) => getAttribute(id, "idtype") === "doi" ||
|
|
231
|
+
id._Name === "doi");
|
|
232
|
+
if (doiIdItem) {
|
|
233
|
+
doiFromItems = getText(doiIdItem);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
pmid: String(pmid),
|
|
239
|
+
title: getItemValue("Title", "String"),
|
|
240
|
+
authors: formatESummaryAuthors(authorsArray),
|
|
241
|
+
source: getItemValue(["Source", "FullJournalName", "SO"], "String"),
|
|
242
|
+
doi: doiFromItems,
|
|
243
|
+
rawPubDate: getItemValue(["PubDate", "ArticleDate"], "Date"),
|
|
244
|
+
rawEPubDate: getItemValue("EPubDate", "Date"),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Extracts and formats brief summaries from ESummary XML result.
|
|
249
|
+
* Handles both DocumentSummarySet (newer) and older DocSum structures.
|
|
250
|
+
* Asynchronously standardizes dates.
|
|
251
|
+
* @param eSummaryResult - The parsed XML object from ESummary (eSummaryResult part).
|
|
252
|
+
* @param context - Request context for logging and passing to date standardization.
|
|
253
|
+
* @returns A promise resolving to an array of parsed brief summary objects.
|
|
254
|
+
*/
|
|
255
|
+
export async function extractBriefSummaries(eSummaryResult, context) {
|
|
256
|
+
if (!eSummaryResult)
|
|
257
|
+
return [];
|
|
258
|
+
const opContext = context ||
|
|
259
|
+
requestContextService.createRequestContext({
|
|
260
|
+
operation: "extractBriefSummariesInternal",
|
|
261
|
+
});
|
|
262
|
+
if (eSummaryResult.ERROR) {
|
|
263
|
+
logger.warning("ESummary result contains an error", {
|
|
264
|
+
...opContext,
|
|
265
|
+
errorDetails: eSummaryResult.ERROR,
|
|
266
|
+
});
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
let rawSummaries = [];
|
|
270
|
+
if (eSummaryResult.DocumentSummarySet?.DocumentSummary) {
|
|
271
|
+
const docSummaries = ensureArray(eSummaryResult.DocumentSummarySet.DocumentSummary);
|
|
272
|
+
rawSummaries = docSummaries
|
|
273
|
+
.map(parseSingleDocumentSummary)
|
|
274
|
+
.filter((s) => s.pmid);
|
|
275
|
+
}
|
|
276
|
+
else if (eSummaryResult.DocSum) {
|
|
277
|
+
const docSums = ensureArray(eSummaryResult.DocSum);
|
|
278
|
+
rawSummaries = docSums.map(parseSingleDocSumOldXml).filter((s) => s.pmid);
|
|
279
|
+
}
|
|
280
|
+
const processedSummaries = [];
|
|
281
|
+
for (const rawSummary of rawSummaries) {
|
|
282
|
+
const pubDate = await standardizeESummaryDate(rawSummary.rawPubDate, opContext);
|
|
283
|
+
const epubDate = await standardizeESummaryDate(rawSummary.rawEPubDate, opContext);
|
|
284
|
+
processedSummaries.push({
|
|
285
|
+
pmid: rawSummary.pmid,
|
|
286
|
+
title: rawSummary.title,
|
|
287
|
+
authors: rawSummary.authors,
|
|
288
|
+
source: rawSummary.source,
|
|
289
|
+
doi: rawSummary.doi,
|
|
290
|
+
pubDate,
|
|
291
|
+
epubDate,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return processedSummaries;
|
|
295
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Barrel file for NCBI XML parsing helper utilities.
|
|
3
|
+
* Re-exports functions from more specific parser modules.
|
|
4
|
+
* @module src/utils/parsing/ncbi-parsing/index
|
|
5
|
+
*/
|
|
6
|
+
export * from "./xmlGenericHelpers.js";
|
|
7
|
+
export * from "./pubmedArticleStructureParser.js";
|
|
8
|
+
export * from "./eSummaryResultParser.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Barrel file for NCBI XML parsing helper utilities.
|
|
3
|
+
* Re-exports functions from more specific parser modules.
|
|
4
|
+
* @module src/utils/parsing/ncbi-parsing/index
|
|
5
|
+
*/
|
|
6
|
+
export * from "./xmlGenericHelpers.js";
|
|
7
|
+
export * from "./pubmedArticleStructureParser.js";
|
|
8
|
+
export * from "./eSummaryResultParser.js";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Helper functions for parsing detailed PubMed Article XML structures,
|
|
3
|
+
* typically from EFetch results.
|
|
4
|
+
* @module src/utils/parsing/ncbi-parsing/pubmedArticleStructureParser
|
|
5
|
+
*/
|
|
6
|
+
import { XmlArticle, XmlAuthorList, XmlGrantList, XmlJournal, XmlKeywordList, XmlMedlineCitation, XmlMeshHeadingList, XmlPublicationTypeList, ParsedArticleAuthor, ParsedJournalInfo, ParsedMeshTerm, ParsedGrant, ParsedArticleDate } from "../../../types-global/pubmedXml.js";
|
|
7
|
+
/**
|
|
8
|
+
* Extracts and formats author information from XML.
|
|
9
|
+
* @param authorListXml - The XML AuthorList element.
|
|
10
|
+
* @returns An array of formatted author objects.
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractAuthors(authorListXml?: XmlAuthorList): ParsedArticleAuthor[];
|
|
13
|
+
/**
|
|
14
|
+
* Extracts and formats journal information from XML.
|
|
15
|
+
* @param journalXml - The XML Journal element from an Article.
|
|
16
|
+
* @param medlineCitationXml - The XML MedlineCitation element (for MedlinePgn).
|
|
17
|
+
* @returns Formatted journal information.
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractJournalInfo(journalXml?: XmlJournal, medlineCitationXml?: XmlMedlineCitation): ParsedJournalInfo | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Extracts and formats MeSH terms from XML.
|
|
22
|
+
* @param meshHeadingListXml - The XML MeshHeadingList element.
|
|
23
|
+
* @returns An array of formatted MeSH term objects.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractMeshTerms(meshHeadingListXml?: XmlMeshHeadingList): ParsedMeshTerm[];
|
|
26
|
+
/**
|
|
27
|
+
* Extracts and formats grant information from XML.
|
|
28
|
+
* @param grantListXml - The XML GrantList element.
|
|
29
|
+
* @returns An array of formatted grant objects.
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractGrants(grantListXml?: XmlGrantList): ParsedGrant[];
|
|
32
|
+
/**
|
|
33
|
+
* Extracts DOI from various possible locations in the XML.
|
|
34
|
+
* Prioritizes ELocationID with ValidYN="Y", then any ELocationID, then ArticleIdList.
|
|
35
|
+
* @param articleXml - The XML Article element.
|
|
36
|
+
* @returns The DOI string or undefined.
|
|
37
|
+
*/
|
|
38
|
+
export declare function extractDoi(articleXml?: XmlArticle): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Extracts publication types from XML.
|
|
41
|
+
* @param publicationTypeListXml - The XML PublicationTypeList element.
|
|
42
|
+
* @returns An array of publication type strings.
|
|
43
|
+
*/
|
|
44
|
+
export declare function extractPublicationTypes(publicationTypeListXml?: XmlPublicationTypeList): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Extracts keywords from XML. Handles single or multiple KeywordList elements.
|
|
47
|
+
* @param keywordListsXml - The XML KeywordList element or an array of them.
|
|
48
|
+
* @returns An array of keyword strings.
|
|
49
|
+
*/
|
|
50
|
+
export declare function extractKeywords(keywordListsXml?: XmlKeywordList[] | XmlKeywordList): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Extracts abstract text from XML. Handles structured abstracts by concatenating sections.
|
|
53
|
+
* If AbstractText is an array, joins them. If it's a single object/string, uses it directly.
|
|
54
|
+
* Prefixes with Label if present.
|
|
55
|
+
* @param abstractXml - The XML Abstract element from an Article.
|
|
56
|
+
* @returns The abstract text string, or undefined if not found or empty.
|
|
57
|
+
*/
|
|
58
|
+
export declare function extractAbstractText(abstractXml?: XmlArticle["Abstract"]): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Extracts PMID from MedlineCitation.
|
|
61
|
+
* @param medlineCitationXml - The XML MedlineCitation element.
|
|
62
|
+
* @returns The PMID string or undefined.
|
|
63
|
+
*/
|
|
64
|
+
export declare function extractPmid(medlineCitationXml?: XmlMedlineCitation): string | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Extracts article dates from XML.
|
|
67
|
+
* @param articleXml - The XML Article element.
|
|
68
|
+
* @returns An array of parsed article dates.
|
|
69
|
+
*/
|
|
70
|
+
export declare function extractArticleDates(articleXml?: XmlArticle): ParsedArticleDate[];
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Helper functions for parsing detailed PubMed Article XML structures,
|
|
3
|
+
* typically from EFetch results.
|
|
4
|
+
* @module src/utils/parsing/ncbi-parsing/pubmedArticleStructureParser
|
|
5
|
+
*/
|
|
6
|
+
import { ensureArray, getText, getAttribute } from "./xmlGenericHelpers.js";
|
|
7
|
+
/**
|
|
8
|
+
* Extracts and formats author information from XML.
|
|
9
|
+
* @param authorListXml - The XML AuthorList element.
|
|
10
|
+
* @returns An array of formatted author objects.
|
|
11
|
+
*/
|
|
12
|
+
export function extractAuthors(authorListXml) {
|
|
13
|
+
if (!authorListXml)
|
|
14
|
+
return [];
|
|
15
|
+
const authors = ensureArray(authorListXml.Author);
|
|
16
|
+
return authors.map((auth) => {
|
|
17
|
+
let affiliation = "";
|
|
18
|
+
const affiliations = ensureArray(auth.AffiliationInfo);
|
|
19
|
+
if (affiliations.length > 0) {
|
|
20
|
+
affiliation = getText(affiliations[0]?.Affiliation);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
lastName: getText(auth.LastName),
|
|
24
|
+
firstName: getText(auth.ForeName), // XML uses ForeName
|
|
25
|
+
initials: getText(auth.Initials),
|
|
26
|
+
affiliation: affiliation || undefined, // Ensure undefined if empty
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extracts and formats journal information from XML.
|
|
32
|
+
* @param journalXml - The XML Journal element from an Article.
|
|
33
|
+
* @param medlineCitationXml - The XML MedlineCitation element (for MedlinePgn).
|
|
34
|
+
* @returns Formatted journal information.
|
|
35
|
+
*/
|
|
36
|
+
export function extractJournalInfo(journalXml, medlineCitationXml) {
|
|
37
|
+
if (!journalXml)
|
|
38
|
+
return undefined;
|
|
39
|
+
const pubDate = journalXml.JournalIssue?.PubDate;
|
|
40
|
+
const year = getText(pubDate?.Year, getText(pubDate?.MedlineDate, "").match(/\d{4}/)?.[0]);
|
|
41
|
+
return {
|
|
42
|
+
title: getText(journalXml.Title),
|
|
43
|
+
isoAbbreviation: getText(journalXml.ISOAbbreviation),
|
|
44
|
+
volume: getText(journalXml.JournalIssue?.Volume),
|
|
45
|
+
issue: getText(journalXml.JournalIssue?.Issue),
|
|
46
|
+
pages: getText(medlineCitationXml?.MedlinePgn) ||
|
|
47
|
+
getText(medlineCitationXml?.Article?.Pagination?.MedlinePgn),
|
|
48
|
+
publicationDate: {
|
|
49
|
+
year: year || undefined,
|
|
50
|
+
month: getText(pubDate?.Month) || undefined,
|
|
51
|
+
day: getText(pubDate?.Day) || undefined,
|
|
52
|
+
medlineDate: getText(pubDate?.MedlineDate) || undefined,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extracts and formats MeSH terms from XML.
|
|
58
|
+
* @param meshHeadingListXml - The XML MeshHeadingList element.
|
|
59
|
+
* @returns An array of formatted MeSH term objects.
|
|
60
|
+
*/
|
|
61
|
+
export function extractMeshTerms(meshHeadingListXml) {
|
|
62
|
+
if (!meshHeadingListXml)
|
|
63
|
+
return [];
|
|
64
|
+
const meshHeadings = ensureArray(meshHeadingListXml.MeshHeading);
|
|
65
|
+
return meshHeadings.map((mh) => {
|
|
66
|
+
const qualifiers = ensureArray(mh.QualifierName);
|
|
67
|
+
const firstQualifier = qualifiers[0];
|
|
68
|
+
// Check MajorTopicYN at DescriptorName, QualifierName, and the root MeshHeading element
|
|
69
|
+
const isMajorDescriptor = getAttribute(mh.DescriptorName, "MajorTopicYN") === "Y";
|
|
70
|
+
const isMajorQualifier = firstQualifier
|
|
71
|
+
? getAttribute(firstQualifier, "MajorTopicYN") === "Y"
|
|
72
|
+
: false;
|
|
73
|
+
// Some schemas might place MajorTopicYN directly on MeshHeading if no qualifiers
|
|
74
|
+
const isMajorRoot = getAttribute(mh, "MajorTopicYN") === "Y";
|
|
75
|
+
return {
|
|
76
|
+
descriptorName: getText(mh.DescriptorName),
|
|
77
|
+
descriptorUi: getAttribute(mh.DescriptorName, "UI"),
|
|
78
|
+
qualifierName: firstQualifier ? getText(firstQualifier) : undefined,
|
|
79
|
+
qualifierUi: firstQualifier
|
|
80
|
+
? getAttribute(firstQualifier, "UI")
|
|
81
|
+
: undefined,
|
|
82
|
+
isMajorTopic: isMajorRoot || isMajorDescriptor || isMajorQualifier,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extracts and formats grant information from XML.
|
|
88
|
+
* @param grantListXml - The XML GrantList element.
|
|
89
|
+
* @returns An array of formatted grant objects.
|
|
90
|
+
*/
|
|
91
|
+
export function extractGrants(grantListXml) {
|
|
92
|
+
if (!grantListXml)
|
|
93
|
+
return [];
|
|
94
|
+
const grants = ensureArray(grantListXml.Grant);
|
|
95
|
+
return grants.map((g) => ({
|
|
96
|
+
grantId: getText(g.GrantID) || undefined,
|
|
97
|
+
agency: getText(g.Agency) || undefined,
|
|
98
|
+
country: getText(g.Country) || undefined,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Extracts DOI from various possible locations in the XML.
|
|
103
|
+
* Prioritizes ELocationID with ValidYN="Y", then any ELocationID, then ArticleIdList.
|
|
104
|
+
* @param articleXml - The XML Article element.
|
|
105
|
+
* @returns The DOI string or undefined.
|
|
106
|
+
*/
|
|
107
|
+
export function extractDoi(articleXml) {
|
|
108
|
+
if (!articleXml)
|
|
109
|
+
return undefined;
|
|
110
|
+
// Check ELocationID first
|
|
111
|
+
const eLocationIDs = ensureArray(articleXml.ELocationID);
|
|
112
|
+
// Prioritize valid DOI
|
|
113
|
+
for (const eloc of eLocationIDs) {
|
|
114
|
+
if (getAttribute(eloc, "EIdType") === "doi" &&
|
|
115
|
+
getAttribute(eloc, "ValidYN") === "Y") {
|
|
116
|
+
const doi = getText(eloc);
|
|
117
|
+
if (doi)
|
|
118
|
+
return doi;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Fallback to any DOI in ELocationID
|
|
122
|
+
for (const eloc of eLocationIDs) {
|
|
123
|
+
if (getAttribute(eloc, "EIdType") === "doi") {
|
|
124
|
+
const doi = getText(eloc);
|
|
125
|
+
if (doi)
|
|
126
|
+
return doi;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Check ArticleIdList as a secondary source
|
|
130
|
+
const articleIds = ensureArray(articleXml.ArticleIdList?.ArticleId);
|
|
131
|
+
for (const aid of articleIds) {
|
|
132
|
+
if (getAttribute(aid, "IdType") === "doi") {
|
|
133
|
+
const doi = getText(aid);
|
|
134
|
+
if (doi)
|
|
135
|
+
return doi;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Extracts publication types from XML.
|
|
142
|
+
* @param publicationTypeListXml - The XML PublicationTypeList element.
|
|
143
|
+
* @returns An array of publication type strings.
|
|
144
|
+
*/
|
|
145
|
+
export function extractPublicationTypes(publicationTypeListXml) {
|
|
146
|
+
if (!publicationTypeListXml)
|
|
147
|
+
return [];
|
|
148
|
+
const pubTypes = ensureArray(publicationTypeListXml.PublicationType);
|
|
149
|
+
return pubTypes.map((pt) => getText(pt)).filter(Boolean);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Extracts keywords from XML. Handles single or multiple KeywordList elements.
|
|
153
|
+
* @param keywordListsXml - The XML KeywordList element or an array of them.
|
|
154
|
+
* @returns An array of keyword strings.
|
|
155
|
+
*/
|
|
156
|
+
export function extractKeywords(keywordListsXml) {
|
|
157
|
+
if (!keywordListsXml)
|
|
158
|
+
return [];
|
|
159
|
+
const lists = ensureArray(keywordListsXml);
|
|
160
|
+
const allKeywords = [];
|
|
161
|
+
for (const list of lists) {
|
|
162
|
+
const keywords = ensureArray(list.Keyword);
|
|
163
|
+
keywords.forEach((kw) => {
|
|
164
|
+
const keywordText = getText(kw);
|
|
165
|
+
if (keywordText) {
|
|
166
|
+
allKeywords.push(keywordText);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return allKeywords;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Extracts abstract text from XML. Handles structured abstracts by concatenating sections.
|
|
174
|
+
* If AbstractText is an array, joins them. If it's a single object/string, uses it directly.
|
|
175
|
+
* Prefixes with Label if present.
|
|
176
|
+
* @param abstractXml - The XML Abstract element from an Article.
|
|
177
|
+
* @returns The abstract text string, or undefined if not found or empty.
|
|
178
|
+
*/
|
|
179
|
+
export function extractAbstractText(abstractXml) {
|
|
180
|
+
if (!abstractXml || !abstractXml.AbstractText)
|
|
181
|
+
return undefined;
|
|
182
|
+
const abstractTexts = ensureArray(abstractXml.AbstractText);
|
|
183
|
+
if (abstractTexts.length === 0)
|
|
184
|
+
return undefined;
|
|
185
|
+
const processedTexts = abstractTexts
|
|
186
|
+
.map((at) => {
|
|
187
|
+
// AbstractText can be string directly or object
|
|
188
|
+
if (typeof at === "string") {
|
|
189
|
+
return at;
|
|
190
|
+
}
|
|
191
|
+
// If it's an object, it should have #text or Label
|
|
192
|
+
let sectionText = getText(at); // Handles at["#text"]
|
|
193
|
+
const label = getAttribute(at, "Label");
|
|
194
|
+
if (label && sectionText) {
|
|
195
|
+
return `${label.trim()}: ${sectionText.trim()}`;
|
|
196
|
+
}
|
|
197
|
+
return sectionText.trim();
|
|
198
|
+
})
|
|
199
|
+
.filter(Boolean); // Remove any empty strings resulting from empty sections
|
|
200
|
+
if (processedTexts.length === 0)
|
|
201
|
+
return undefined;
|
|
202
|
+
return processedTexts.join("\n\n").trim() || undefined; // Join sections with double newline
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Extracts PMID from MedlineCitation.
|
|
206
|
+
* @param medlineCitationXml - The XML MedlineCitation element.
|
|
207
|
+
* @returns The PMID string or undefined.
|
|
208
|
+
*/
|
|
209
|
+
export function extractPmid(medlineCitationXml) {
|
|
210
|
+
if (!medlineCitationXml || !medlineCitationXml.PMID)
|
|
211
|
+
return undefined;
|
|
212
|
+
return getText(medlineCitationXml.PMID);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Extracts article dates from XML.
|
|
216
|
+
* @param articleXml - The XML Article element.
|
|
217
|
+
* @returns An array of parsed article dates.
|
|
218
|
+
*/
|
|
219
|
+
export function extractArticleDates(articleXml) {
|
|
220
|
+
if (!articleXml || !articleXml.ArticleDate)
|
|
221
|
+
return [];
|
|
222
|
+
const articleDatesXml = ensureArray(articleXml.ArticleDate);
|
|
223
|
+
return articleDatesXml.map((ad) => ({
|
|
224
|
+
dateType: getAttribute(ad, "DateType"),
|
|
225
|
+
year: getText(ad.Year),
|
|
226
|
+
month: getText(ad.Month),
|
|
227
|
+
day: getText(ad.Day),
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Generic helper functions for parsing XML data, particularly
|
|
3
|
+
* structures from fast-xml-parser.
|
|
4
|
+
* @module src/utils/parsing/ncbi-parsing/xmlGenericHelpers
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Ensures that the input is an array. If it's not an array, it wraps it in one.
|
|
8
|
+
* Handles undefined or null by returning an empty array.
|
|
9
|
+
* @param item - The item to ensure is an array.
|
|
10
|
+
* @returns An array containing the item, or an empty array if item is null/undefined.
|
|
11
|
+
* @template T - The type of the items in the array.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureArray<T>(item: T | T[] | undefined | null): T[];
|
|
14
|
+
/**
|
|
15
|
+
* Safely extracts text content from an XML element, which might be a string or an object with a "#text" property.
|
|
16
|
+
* Handles cases where #text might be a number or boolean by converting to string.
|
|
17
|
+
* @param element - The XML element (string, object with #text, or undefined).
|
|
18
|
+
* @param defaultValue - The value to return if text cannot be extracted. Defaults to an empty string.
|
|
19
|
+
* @returns The text content or the default value.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getText(element: any, defaultValue?: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Safely extracts an attribute value from an XML element.
|
|
24
|
+
* Assumes attributes are prefixed with "@_" by fast-xml-parser.
|
|
25
|
+
* @param element - The XML element object.
|
|
26
|
+
* @param attributeName - The name of the attribute (e.g., "_UI", "_MajorTopicYN", without the "@_" prefix).
|
|
27
|
+
* @param defaultValue - The value to return if the attribute is not found. Defaults to an empty string.
|
|
28
|
+
* @returns The attribute value or the default value.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getAttribute(element: any, attributeName: string, // e.g., "UI", "MajorTopicYN"
|
|
31
|
+
defaultValue?: string): string;
|