Skip to content

ivicamil/ArxivKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ArxivKit

ArxivKit is a Swift DSL wrapper for arXiv API. It includes an embedded Domain-specific language for constructing arXiv queries in a type-safe way. For detailed explanation of all the available APIs see project documentation page.

This project is not affiliated with arXiv.org.

Supported Platforms

Minimal version of Swift required for building the package is 5.3. Following platforms are supported:

  • macOS (from v11.0)
  • iOS, iPadOS, Mac Catalyst (from v14.0)
  • Linux (tested on Ubuntu 20.04)

Installation

To use in an Xcode project, add https://github.com/ivicamil/ArxivKit.git as package dependency as explained in Apple developer documentation.

In package-based projects, add ArxivKit dependency to Package.swift as explained bellow:

// swift-tools-version:5.3

import PackageDescription

let package = Package(
    name: "ProjectName",
    platforms: [],
    dependencies: [
        // Replace the version string with your own desired minimal version.
        .package(url: "https://github.com/ivicamil/ArxivKit.git", from: "2.0.0")
    ],
    targets: [
        .target(
            name: "ProjectName",
            dependencies: ["ArxivKit"])
    ]
)

Usage

Networking

ArxivKit uses URLSession for sending API requests. It is strongly recommended to reuse a single session instance for multiple related tasks. For detailed information about creating, configuring and using URLSession and related APIs see Apple Developer documentation for the class. In many cases, including all the examples from this document, a session with default configuration is enough:

let session = URLSession(configuration: .default)

or

let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)

ArxivKit defines convenience extension methods on URLSession and ArxivRequest that create URLSessionDataTask for sending arXiv API requests and parsing received response. In more advanced cases, clients can implement their own custom networking layer and still use this library as a Domain Specific Language for constructing ArxivRequest values. In that case, ArxivParser can be used for parsing raw API responses to ArxivReponse values.

Arxiv Query

ArxivQuery type specifies different possible information that can be searched on arXiv. ArxivQuery conforms to ArxivRequest protocol and it can be used to create and run fetch tasks. Before fetching, an ArxivRequest can be configured with various modifier methods to define desired number of articles per page, sorting order and criterion etc.

ArxivKit enables construction of all the search queries available in Arxiv API. Articles can be searched for a term in any of the available fields, by arXiv subject or by publication and update dates. All the subjects are available as constants under following namespaces:

  • Physics
  • Astrophysics
  • CondensedMatter
  • NonlinearSciences
  • OtherPhysicsSubjects
  • Mathematics
  • ComputerScience
  • QuantitativeBiology
  • ElectricalEngineeringAndSystemsScience
  • Statistics
  • QuantitativeFinance
  • Economy

Queries are constructed by using an embedded Domain Specific Language created with Swift feature called Result Builders, that was first used in Apple's SwiftUI Framework. The DSL enables creating arbitrarily complex query trees by using an intuitive syntax.

After a query is constructed and configured, fetch(using:completion:) or other related methods can be used to construct and run a fetch task. If no error occurs, fetch task returns an ArxivResponse, a parsed arXiv API atom feed. The response stores various metadata and a list of ArxivEntry values. Each entry stores information about a single arXiv article, such as its title, abstract, authors, PDF link etc.

Bellow are the examples of few common use scenarios.

Performing Search Using async/await (iOS 15, macOS 12 or greater)

do {
    let response = try await term("electron").fetch(using: session)
    let articles = response.entries
    print("Found \(response.totalResults) articles")
    print()
    print(articles.map { $0.title }.joined(separator: "\n\n"))
    
} catch {
    print("Could not fetch articles: \(error.localizedDescription)")
}

Performing Search Using Completion Handler

term("electron").fetch(using: session) { result in
    switch result {
    case let .success(response):
        let articles = response.entries
        print("Found \(response.totalResults) articles")
        print()
        print(articles.map { $0.title }.joined(separator: "\n\n"))
    case let .failure(error):
        print("Could not fetch articles: \(error.localizedDescription)")
    }
}

Simple Queries

term("dft")
term("dft", in: .title)
term("dft", in: .abstract)
term("Feynman", in: .authors)
term("20 pages", in: .comment)
term("AMS", in: .journalReference)

subject(Physics.computationalPhysics)

submitted(in: .past(.month))
lastUpdated(in: .past(5, unit: .day))

Complex Queries

all {
    any {
        term("dft", in: .title)
        term(#""ab initio""#, in: .title)
    }
    subject(Physics.computationalPhysics)
}
.excluding {
    term("conductivity", in: .title)
}

Fetching Specific Articles

ArxivIdList("2101.02212", "2101.02215")
    .fetch(using: session) { result in
        // Do something with the result
    }

Fetching All Versions of an Article

let entry: ArxivEntry = ...

ArxivIdList(ids: entry.allVersionsIDs)
    .fetch(using: session) { result in
        // Do something with the result
    }

Paging Example

var currentResponse: ArxivResponse?

func fetchElectronArticles(startIndex i: Int) {
    term("electron")
        .itemsPerPage(20)
        .startIndex(i)
        .fetch(using: session) { result in
            switch result {
            case let .success(response):
                currentResponse = response
                print("Page \(response.currentPage) fetched.")
            case let .failure(error):
                // Deal with error
            }
        }
}

// Fetch the first page
fetchElectronArticles(startIndex: 0)

// When we are sure that the first page is fetched, we can fetch the second page
if let response = currentResponse, let secondPageIndex = response.nextPageStartIndex {
    fetchElectronArticles(startIndex: secondPageIndex)
}

Configuring Request

term("electron")
    .sorted(by: .relevance)
    .sortingOrder(.descending)
    .itemsPerPage(20)
    .startIndex(60)
    .fetch(using: session) { result in
        // Do something with the result
    }

Advanced Processing of ArxivKit Results with Combine (Apple Platforms Only)

On Apple platforms, Combine framework can be used for advanced processing of URL Session data task results, as explained in this Apple Developer Documentation article. ArxivKit works naturally with Combine. Bellow is a simple example of creating a publisher for receiving arXiv API results and subscribing to it:

import Combine

var cancelable: AnyCancellable?

cancelable = session
    .dataTaskPublisher(for: term("electronic configuration").url)
    .tryMap(ArxivParser().parse)
    .receive(on: DispatchQueue.main)
    .sink(
        receiveCompletion: { completion in
            let outcome: String
            switch completion {
            case .finished:
                outcome = "successfully"
            case let .failure(error):
                outcome = "with error: \(error)"
            }
            print("Publisher completed \(outcome).")
        },
        receiveValue: { response in
            print ("Received arXiv feed:\n\n \(response)\n")
        }
    )

License

ArxivKit is released under MIT license. For terms and conditions of using the arXiv API itself see arXiv API Help.