diff --git a/Google Scholar.js b/Google Scholar.js index 7ccf8367fbb..3bab1814baf 100644 --- a/Google Scholar.js +++ b/Google Scholar.js @@ -9,13 +9,14 @@ "inRepository": true, "translatorType": 4, "browserSupport": "gcsibv", - "lastUpdated": "2022-02-25 01:19:36" + "lastUpdated": "2023-07-11 07:58:52" } /* ***** BEGIN LICENSE BLOCK ***** - Copyright © 2022 Simon Kornblith, Frank Bennett, Aurimas Vinckevicius + Copyright © 2022 Simon Kornblith, Frank Bennett, Aurimas Vinckevicius, and + Zoë C. Ma. This file is part of Zotero. @@ -35,6 +36,31 @@ ***** END LICENSE BLOCK ***** */ +const DELAY_INTERVAL = 2000; // in milliseconds + +var GS_CONFIG = { baseURL: undefined, lang: undefined }; + +const MIME_TYPES = { + PDF: 'application/pdf', + DOC: 'application/msword', + HTML: 'text/html', +}; + +// The only "typedef" that needs to be kept in mind: a data object representing +// a row in the seach/profile listing. + +/** + * Information object for one Google Scholar entry or "row" + * + * @typedef {Object} RowObj + * @property {?string} id - Google Scholar ID string + * @property {string} [directLink] - href of the title link + * @property {string} [attachmentLink] - href of the attachment link found by GS + * @property {string} [attachmentType] - type (file extension) of the attachment + * @property {string} [byline] - the line of text below the title (in green) + */ + + /* Detection for law cases, but not "How cited" pages, * e.g. url of "how cited" page: * http://scholar.google.co.jp/scholar_case?about=1101424605047973909&q=kelo&hl=en&as_sdt=2002 @@ -74,12 +100,12 @@ function getSearchResults(doc, checkOnly) { var found = false; var rows = doc.querySelectorAll('.gs_r[data-cid]'); for (var i = 0; i < rows.length; i++) { - var href = rows[i].dataset.cid; + var id = rows[i].dataset.cid; var title = text(rows[i], '.gs_rt'); - if (!href || !title) continue; + if (!id || !title) continue; if (checkOnly) return true; found = true; - items[href] = title; + items[id] = title; } return found ? items : false; } @@ -101,198 +127,233 @@ function getProfileResults(doc, checkOnly) { } -function doWeb(doc, url) { - var type = detectWeb(doc, url); +async function doWeb(doc, url) { + // Determine the domain and language variant of the page. + let urlObj = new URL(url); + GS_CONFIG.baseURL = urlObj.origin; + GS_CONFIG.lang = urlObj.searchParams.get("hl") || "en"; + + let type = detectWeb(doc, url); + if (type == "multiple") { - if (getSearchResults(doc, true)) { - Zotero.selectItems(getSearchResults(doc, false), function (items) { - if (!items) { - return; - } - var ids = []; - for (var i in items) { - ids.push(i); - } - // here it is enough to know the ids and we can call scrape directly - scrape(doc, ids); - }); + let referrerURL; + let getRow; + let keys; + + if (getSearchResults(doc, true/* checkOnly */)) { + let items = await Z.selectItems(getSearchResults(doc, false)); + if (!items) { + return; + } + referrerURL = new URL(doc.location); + getRow = rowFromSearchResult; + keys = Object.keys(items); } - else if (getProfileResults(doc, true)) { - Zotero.selectItems(getProfileResults(doc, false), function (items) { - if (!items) { - return; - } - var articles = []; - for (var i in items) { - articles.push(i); - } - // here we need open these pages before calling scrape - ZU.processDocuments(articles, scrape); - }); + else if (getProfileResults(doc, true/* checkOnly */)) { + let urls = await Z.selectItems(getProfileResults(doc, false)); + if (!urls) { + return; + } + const profileName = text(doc, "#gsc_prf_in"); + referrerURL = getEmulatedSearchURL(profileName); + getRow = rowFromProfile; + keys = Object.keys(urls); } + + await scrapeMany(keys, doc, getRow, referrerURL); } else { // e.g. https://scholar.google.de/citations?view_op=view_citation&hl=de&user=INQwsQkAAAAJ&citation_for_view=INQwsQkAAAAJ:u5HHmVD_uO8C - scrape(doc, url, type); + await scrape(doc, url, type); } } -function scrape(doc, idsOrUrl, type) { - if (Array.isArray(idsOrUrl)) { - scrapeIds(doc, idsOrUrl); +// Scrape an array of string IDs or URLs (keys) that are obtained from +// the GS search/profile document (baseDocument). rowRequestor is a function +// that returns the row or a promise resolving to a row when called as +// rowRequestor(key, baseDocument). +// This function will reject if some rows failed to translate. +async function scrapeMany(keys, baseDocument, rowRequestor, referrerURL) { + let failedRows = []; + let promises = []; + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + let row = await rowRequestor(key, baseDocument); + if (row) { + // NOTE: here we start a promise that scrapes the row in the stages + // of DOI -> arXiv -> Google Scholar, but don't wait for it in the + // loop over rows + promises.push(scrapeInStages(row, referrerURL, failedRows)); + } + if (i < keys.length - 1) { + // But we do wait between iterations over the rows + await delay(DELAY_INTERVAL); + } } - else if (type && type == "case") { - scrapeCase(doc, idsOrUrl); + await Promise.all(promises); + if (failedRows.length) { + throw new Error(`${failedRows.length} row(s) failed to translate`); + } +} + + +// Scrape one GS entry +async function scrape(doc, url, type) { + if (type && type == "case") { + scrapeCase(doc, url); } else { - var related = ZU.xpathText(doc, '//a[contains(@href, "q=related:")]/@href'); - if (!related) { - throw new Error("Could not locate related URL"); - } - var itemID = related.match(/=related:([^:]+):/); - if (itemID) { - scrapeIds(doc, [itemID[1]]); + // Stand-alone "View article" page + const profileName = text(doc, "#gsc_sb_ui > div > a"); + let referrerURL = getEmulatedSearchURL(profileName); + // Single-item row computed from "View article" page content. + let row = parseViewArticle(doc); + if (row) { + let failedRow = []; + await scrapeInStages(row, referrerURL, failedRow); + if (failedRow.length) { + throw new Error(`Failed to translate: ${row}`); + } } else { - Z.debug("Can't find itemID. related URL is " + related); - throw new Error("Cannot extract itemID from related link"); + throw new Error(`Expected 'View article' page at ${url}, but failed to extract article info from it.`); } } } +// "row requestor" functions +// For search results - given ID and the document it originates, return a row. +// This function does not incur additional network requests. +function rowFromSearchResult(id, doc) { + try { + let entryElem = doc.querySelector(`.gs_r[data-cid="${id}"]`); + // href from an tag, direct link to the source. Note that the ID + // starting with number can be fine, but the selector is a pain. + let aElem = doc.getElementById(id); + let directLink = aElem ? aElem.href : undefined; + let attachmentLink = attr(entryElem, ".gs_ggs a", "href"); + let attachmentType = text(entryElem, ".gs_ctg2"); + if (attachmentType) { + // Remove the brackets + attachmentType = attachmentType.slice(1, -1).toUpperCase(); + } + let byline = text(entryElem, ".gs_a"); -function scrapeIds(doc, ids) { - for (let i = 0; i < ids.length; i++) { - // We need here 'let' to access ids[i] later in the nested functions - let context = doc.querySelector('.gs_r[data-cid="' + ids[i] + '"]'); - if (!context && ids.length == 1) context = doc; - var citeUrl = '/scholar?q=info:' + ids[i] + ':scholar.google.com/&output=cite&scirp=1'; - // For 'My Library' we check the search field at the top - // and then in these cases change the citeUrl accordingly. - var scilib = attr(doc, '#gs_hdr_frm input[name="scilib"]', 'value'); - if (scilib && scilib == 1) { - citeUrl = '/scholar?scila=' + ids[i] + '&output=cite&scirp=1'; + return { id, directLink, attachmentLink, attachmentType, byline }; + } + catch (error) { + Z.debug(`Warning: failed to get row info for GS id ${id}`); + return undefined; + } +} + +// For search results - given "Article view" URLs and the profile document it +// originates, return a row. This will incur one request (to get the "Article +// view" document) per row. +async function rowFromProfile(url, profileDoc) { + // To "navigate" to the linked "View article" page from the profile page, a + // referrer is sent as header in the request + const requestOptions = { headers: { Referer: profileDoc.location.href } }; + + try { + let viewArticleDoc = await requestDocument(url, requestOptions); + let row = parseViewArticle(viewArticleDoc); + if (row) { + return row; } - ZU.doGet(citeUrl, function (citePage) { - var m = citePage.match(/href="((https?:\/\/[a-z.]*)?\/scholar.bib\?[^"]+)/); - if (!m) { - // Saved lists and possibly other places have different formats for BibTeX URLs - // Trying to catch them here (can't add test bc lists are tied to google accounts) - m = citePage.match(/href="(.+?)">BibTeX<\/a>/); - } - if (!m) { - var msg = "Could not find BibTeX URL"; - var title = citePage.match(/(.*?)<\/title>/i); - if (title) { - if (title) msg += ' Got page with title "' + title[1] + '"'; - } - throw new Error(msg); - } - var bibUrl = ZU.unescapeHTML(m[1]); - ZU.doGet(bibUrl, function (bibtex) { - var translator = Zotero.loadTranslator("import"); - translator.setTranslator("9cb70025-a888-4a29-a210-93ec52da40d4"); - translator.setString(bibtex); - translator.setHandler("itemDone", function (obj, item) { - // these two variables are extracted from the context - var titleLink = attr(context, 'h3 a, #gsc_vcd_title a', 'href'); - var secondLine = text(context, '.gs_a') || ''; - // case are not recognized and can be characterized by the - // titleLink, or that the second line starts with a number - // e.g. 1 Cr. 137 - Supreme Court, 1803 - if ((titleLink && titleLink.includes('/scholar_case?')) - || secondLine && ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].includes(secondLine[0])) { - item.itemType = "case"; - item.caseName = item.title; - item.reporter = item.publicationTitle; - item.reporterVolume = item.volume; - item.dateDecided = item.date; - item.court = item.publisher; - } - // patents are not recognized but are easily detected - // by the titleLink or second line - if ((titleLink && titleLink.includes('google.com/patents/')) || secondLine.includes('Google Patents')) { - item.itemType = "patent"; - // authors are inventors - for (let i = 0, n = item.creators.length; i < n; i++) { - item.creators[i].creatorType = 'inventor'; - } - // country and patent number - if (titleLink) { - let m = titleLink.match(/\/patents\/([A-Za-z]+)(.*)$/); - if (m) { - item.country = m[1]; - item.patentNumber = m[2]; - } - } - } - - // fix titles in all upper case, e.g. some patents in search results - if (item.title.toUpperCase() == item.title) { - item.title = ZU.capitalizeTitle(item.title); - } - - // delete "others" as author - if (item.creators.length) { - var lastCreatorIndex = item.creators.length - 1, - lastCreator = item.creators[lastCreatorIndex]; - if (lastCreator.lastName === "others" && (lastCreator.fieldMode === 1 || lastCreator.firstName === "")) { - item.creators.splice(lastCreatorIndex, 1); - } - } - - // clean author names - for (let j = 0, m = item.creators.length; j < m; j++) { - if (!item.creators[j].firstName) continue; - - item.creators[j] = ZU.cleanAuthor( - item.creators[j].lastName + ', ' - + item.creators[j].firstName, - item.creators[j].creatorType, - true); - } - - // attach linked document as attachment if available - var documentLinkTarget = attr(context, '.gs_or_ggsm a, #gsc_vcd_title_gg a', 'href'); - var documentLinkTitle = text(context, '.gs_or_ggsm a, #gsc_vcd_title_gg a'); - if (documentLinkTarget) { - // Z.debug(documentLinkTarget); - var attachment = { - title: "Full Text", - url: documentLinkTarget - }; - let m = documentLinkTitle.match(/^\[(\w+)\]/); - if (m) { - var mimeTypes = { - PDF: 'application/pdf', - DOC: 'application/msword', - HTML: 'text/html' - }; - if (Object.keys(mimeTypes).includes(m[1].toUpperCase())) { - attachment.mimeType = mimeTypes[m[1]]; - } - } - item.attachments.push(attachment); - } - - // Attach linked page as snapshot if available - if (titleLink && titleLink != documentLinkTarget) { - item.attachments.push({ - url: titleLink, - title: "Snapshot", - mimeType: "text/html" - }); - } + } + catch (error) { + Z.debug(`Warning: cannot retrieve the profile view-article page at ${url}; skipping. The error was:`); + Z.debug(error); + return undefined; + } - item.complete(); - }); - translator.translate(); - }); - }); + Z.debug(`Warning: cannot find Google Scholar id in profile view-article page at ${url}; skipping.`); + return undefined; +} + +// process the row in the order of DOI -> arXiv -> GS. If all fail, add the row +// to the array failedRows. This function never rejects. +async function scrapeInStages(row, referrerURL, failedRows) { + try { + await scrapeDOI(row); + return; + } + catch (error) { + } + + try { + await scrapeArXiv(row); + return; + } + catch (error) { + } + + try { + await scrapeGoogleScholar(row, referrerURL); + } + catch (error) { + Z.debug(`Error with Google Scholar scraping of row ${row.directLink}`); + Z.debug(`The error was: ${error}`); + failedRows.push(row); } } +function scrapeDOI(row) { + let doi = extractDOI(row); + if (!doi) { + throw new Error(`No DOI found for link: ${row.directLink}`); + } + + let translate = Z.loadTranslator("search"); + // DOI Content Negotiation + translate.setTranslator("b28d0d42-8549-4c6d-83fc-8382874a5cb9"); + translate.setHandler("error", () => {}); + translate.setHandler("itemDone", (obj, item) => { + // NOTE: The 'DOI Content Negotiation' translator does not add + // attachments on its own + addAttachment(item, row); + item.complete(); + }); + translate.setSearch({ DOI: doi }); + Z.debug(`Trying DOI search for ${row.directLink}`); + return translate.translate(); +} + +function scrapeArXiv(row) { + let eprintID = extractArXiv(row); + if (!eprintID) { + throw new Error(`No ArXiv eprint ID found for link: ${row.directLink}`); + } + + let translate = Z.loadTranslator("search"); + // arXiv.org + translate.setTranslator("ecddda2e-4fc6-4aea-9f17-ef3b56d7377a"); + translate.setHandler("error", () => {}); + translate.setHandler("itemDone", (obj, item) => { + // NOTE: Attachment is handled by the arXiv.org search translator + item.complete(); + }); + translate.setSearch({ arXiv: eprintID }); + Z.debug(`Trying ArXiv search for ${row.directLink}`); + return translate.translate(); +} + +function scrapeGoogleScholar(row, referrerURL) { + // URL of the citation-info page fragment for the current row + let citeURL; + + if (referrerURL.searchParams.get("scilib") === "1") { // My Library + citeURL = `${GS_CONFIG.baseURL}/scholar?scila=${row.id}&output=cite&scirp=0&hl=${GS_CONFIG.lang}`; + } + else { // Normal search page + citeURL = `${GS_CONFIG.baseURL}/scholar?q=info:${row.id}:scholar.google.com/&output=cite&scirp=0&hl=${GS_CONFIG.lang}`; + } + + Z.debug(`Falling back to Google Scholar scraping for ${row.directLink || "citation-only entry"}`); + return processCitePage(citeURL, row, referrerURL.href); +} /* * ######################### @@ -661,6 +722,237 @@ ItemFactory.prototype.saveItemCommonVars = function () { } }; + +/* + * ######################### + * ### Utility Functions ### + * ######################### + */ + +// Returns a promise that resolves (to undefined) after the minimum time delay +// specified in milliseconds +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// Identification functions for external searches + +/** + * Extract candidate DOI from row by parsing its direct-link URL + * + * @param {RowObj} row + * @returns {string?} Candidate DOI string, or null if not found + */ +function extractDOI(row) { + let path = decodeURIComponent((new URL(row.directLink)).pathname); + // Normally, match to the end of the path, because we couldn't have known + // better. + // But we can try clean up a bit, for common file extensions tacked to the + // end, e.g. the link in the header title of + // https://scholar.google.com/citations?view_op=view_citation&hl=en&user=Cz6X6UYAAAAJ&citation_for_view=Cz6X6UYAAAAJ:zYLM7Y9cAGgC + // https://www.nomos-elibrary.de/10.5771/9783845229614-153.pdf + let m = path.match(/(10\.\d{4,}\/.+?)(?:[./](?:pdf|htm|html|xhtml|epub|xml))?$/i); + return m && m[1]; +} + +/** + * Extract arXiv ID from row by parsing its direct-link URL + * + * @param {RowObj} row + * @returns {string?} ArXiv ID, or null if not found + */ +function extractArXiv(row) { + let urlObj = new URL(row.directLink); + if (urlObj.hostname.toLowerCase() !== "arxiv.org") { + return null; + } + let path = decodeURIComponent(urlObj.pathname); + let m = path.match(/\/\w+\/([a-z-]+\/\d+|\d+\.\d+)$/i); + return m && m[1]; +} + +// Page-processing utilities + +/** + * Returns an emulated search URL for a GS search with the profile name as the + * search term + * + * @param {string} profileName - Name of the profile's owner + * @returns {URL} + */ +function getEmulatedSearchURL(profileName) { + return new URL(`/scholar?hl=${GS_CONFIG.lang}&as_sdt=0%2C5&q=${encodeURIComponent(profileName).replace(/%20/g, "+")}&btnG=`, GS_CONFIG.baseURL); +} + +/** + * Parse the "View article" page and returns the equivalent of a GS + * search-result row + * + * @param {Document} viewArticleDoc - "View article" document + * @returns {RowObj?} The row object, or null if parsing failed. + */ +function parseViewArticle(viewArticleDoc) { + let related = ZU.xpathText(viewArticleDoc, + '//a[contains(@href, "q=related:")]/@href'); + if (!related) { + Z.debug("Could not locate 'related' link on the 'View article' page."); + return null; + } + + let m = related.match(/=related:([^:]+):/); // GS id + if (m) { + let id = m[1]; + let directLink = attr(viewArticleDoc, ".gsc_oci_title_link", "href"); + let attachmentLink = attr(viewArticleDoc, "#gsc_oci_title_gg a", "href"); + let attachmentType = text(viewArticleDoc, ".gsc_vcd_title_ggt"); + if (attachmentType) { + attachmentType = attachmentType.slice(1, -1).toUpperCase(); + } + return { id, directLink, attachmentLink, attachmentType }; + } + else { + Z.debug("Unexpected format of 'related' URL; can't find Google Scholar id. 'related' URL is " + related); + return null; + } +} + +/** + * Request and read the page-fragment with citation info, retrieve BibTeX, and + * import. Each call sends two network requests, and each request is preceded + * by a delay. + * + * @param {string} citeURL - The citation-info page fragment's URL, to be + * requested. + * @param {RowObj} row - The row object carrying the information of the entry's + * identity. + * @param {string} referrer - The referrer for the citation-info page fragment + * request. + */ +async function processCitePage(citeURL, row, referrer) { + let requestOptions = { headers: { Referer: referrer } }; + // Note that the page at citeURL has no doctype and is not a complete HTML + // document. The browser can parse it in quirks mode but ZU.requestDocument + // has trouble with it. + await delay(DELAY_INTERVAL); + const citePage = await requestText(citeURL, requestOptions); + + let m = citePage.match(/href="((https?:\/\/[a-z.]*)?\/scholar.bib\?[^"]+)/); + if (!m) { + // Saved lists and possibly other places have different formats for + // BibTeX URLs + // Trying to catch them here (can't add test bc lists are tied to + // google accounts) + m = citePage.match(/href="(.+?)">BibTeX<\/a>/); + } + if (!m) { + var msg = "Could not find BibTeX URL"; + var title = citePage.match(/<title>(.*?)<\/title>/i); + if (title) { + msg += ' Got page with title "' + title[1] + '"'; + } + throw new Error(msg); + } + const bibTeXURL = ZU.unescapeHTML(m[1]); + + // Pause between obtaining the citation info page and sending the request + // for the BibTeX document + await delay(DELAY_INTERVAL); + + // NOTE: To emulate the web app, the referrer for the BibTeX text is always + // set to the origin (e.g. https://scholar.google.com/), imitating + // strict-origin-when-cross-origin + requestOptions.headers.Referer = GS_CONFIG.baseURL + "/"; + const bibTeXBody = await requestText(bibTeXURL, requestOptions); + + let translator = Z.loadTranslator("import"); + translator.setTranslator("9cb70025-a888-4a29-a210-93ec52da40d4"); // BibTeX + translator.setString(bibTeXBody); + translator.setHandler("itemDone", function (obj, item) { + // case are not recognized and can be characterized by the + // title link, or that the second line starts with a number + // e.g. 1 Cr. 137 - Supreme Court, 1803 + if ((row.directLink && row.directLink.includes('/scholar_case?')) + || row.byline && "01234567890".includes(row.byline[0])) { + item.itemType = "case"; + item.caseName = item.title; + item.reporter = item.publicationTitle; + item.reporterVolume = item.volume; + item.dateDecided = item.date; + item.court = item.publisher; + } + // patents are not recognized but are easily detected + // by the titleLink or second line + if ((row.directLink && row.directLink.includes('google.com/patents/')) + || (row.byline && row.byline.includes('Google Patents'))) { + item.itemType = "patent"; + // authors are inventors + for (let i = 0, n = item.creators.length; i < n; i++) { + item.creators[i].creatorType = 'inventor'; + } + // country and patent number + if (row.directLink) { + let m = row.directLink.match(/\/patents\/([A-Za-z]+)(.*)$/); + if (m) { + item.country = m[1]; + item.patentNumber = m[2]; + } + } + } + + // Add the title link as the url of the item + if (row.directLink) { + item.url = row.directLink; + } + + // fix titles in all upper case, e.g. some patents in search results + if (item.title.toUpperCase() === item.title) { + item.title = ZU.capitalizeTitle(item.title); + } + + // delete "others" as author + if (item.creators.length) { + var lastCreatorIndex = item.creators.length - 1, + lastCreator = item.creators[lastCreatorIndex]; + if (lastCreator.lastName === "others" && (lastCreator.fieldMode === 1 || lastCreator.firstName === "")) { + item.creators.splice(lastCreatorIndex, 1); + } + } + + // clean author names + for (let j = 0, m = item.creators.length; j < m; j++) { + if (!item.creators[j].firstName) { + continue; + } + + item.creators[j] = ZU.cleanAuthor( + item.creators[j].lastName + ', ' + + item.creators[j].firstName, + item.creators[j].creatorType, + true); + } + + addAttachment(item, row); + + item.complete(); + }); + return translator.translate(); +} + +function addAttachment(item, row) { + // attach linked document as attachment if available + if (row.attachmentLink) { + let attachment = { + title: "Available Version (via Google Scholar)", + url: row.attachmentLink, + }; + let mimeType = MIME_TYPES[row.attachmentType]; + if (mimeType) { + attachment.mimeType = mimeType; + } + item.attachments.push(attachment); + } +} + /* Test Case Descriptions: (these have not been included in the test case JSON below as per aurimasv's comment on https://github.com/zotero/translators/pull/833) @@ -942,10 +1234,11 @@ var testCases = [ { "type": "web", "url": "https://scholar.google.de/citations?view_op=view_citation&hl=de&user=INQwsQkAAAAJ&citation_for_view=INQwsQkAAAAJ:u5HHmVD_uO8C", + "detectedItemType": "journalArticle", "items": [ { - "itemType": "journalArticle", - "title": "Linked data-the story so far", + "itemType": "bookSection", + "title": "Linked data: The story so far", "creators": [ { "firstName": "Christian", @@ -963,17 +1256,17 @@ var testCases = [ "creatorType": "author" } ], - "date": "2009", - "itemID": "bizer2009linked", + "date": "2011", + "bookTitle": "Semantic services, interoperability and web applications: emerging concepts", + "itemID": "bizer2011linked", "libraryCatalog": "Google Scholar", "pages": "205–227", - "publicationTitle": "Semantic services, interoperability and web applications: emerging concepts", + "publisher": "IGI global", + "shortTitle": "Linked data", + "url": "https://www.igi-global.com/chapter/linkeddata-story-far/55046", "attachments": [ { - "title": "Snapshot" - }, - { - "title": "Fulltext", + "title": "Available Version (via Google Scholar)", "mimeType": "application/pdf" } ], @@ -1023,6 +1316,46 @@ var testCases = [ "seeAlso": [] } ] + }, + { + "type": "web", + "url": "https://scholar.google.com/citations?view_op=view_citation&hl=en&user=RjsFKYEAAAAJ&cstart=20&pagesize=80&citation_for_view=RjsFKYEAAAAJ:5nxA0vEk-isC", + "detectedItemType": "journalArticle", + "items": [ + { + "itemType": "journalArticle", + "title": "The Weakness of Power and the Power of Weakness: The Ethics of War in a Time of Terror", + "creators": [ + { + "creatorType": "author", + "firstName": "Michael", + "lastName": "Northcott" + } + ], + "date": "04/2007", + "DOI": "10.1177/0953946806075493", + "ISSN": "0953-9468, 1745-5235", + "abstractNote": "In 2002 a significant number of American theologians declared that the ‘war on terror’ was a just war. But the indiscriminate strategies and munitions technologies deployed in the invasion and occupation of Iraq fall short of the just war principles of non-combatant immunity, and proportionate response. The just war tradition is one of Christendom's most enduring legacies to the law of nations. Its practice implies a standard of virtue in war that is undermined by the indiscriminate effects of many modern weapons and by the deliberate targeting of civilian infrastructure. The violent power represented by the technology of what the Vatican calls ‘total war’has occasioned a significant shift in Catholic social teaching on just war since the Second World War. Total war generates an asymmetry of weakness in those subjected to these techniques of terror, and this has only strengthened the violence of the Islamist struggle against the West. But those who draw inspiration and legitimacy from this weakness in their struggle with the West also reject virtue in war. In a time of terror the theological vocation is to speak peace and to recall the terms in which the peace of God was achieved by way of the cross.", + "issue": "1", + "journalAbbreviation": "Studies in Christian Ethics", + "language": "en", + "libraryCatalog": "DOI.org (Crossref)", + "pages": "88-101", + "publicationTitle": "Studies in Christian Ethics", + "shortTitle": "The Weakness of Power and the Power of Weakness", + "url": "http://journals.sagepub.com/doi/10.1177/0953946806075493", + "volume": "20", + "attachments": [ + { + "title": "Available Version (via Google Scholar)", + "mimeType": "application/pdf" + } + ], + "tags": [], + "notes": [], + "seeAlso": [] + } + ] } ] /** END TEST CASES **/