amateur-radio logging library for Java, including an ADIF decoder and LISP-based scoring framework for contests.

View on GitHub

qxsl: Amateur-Radio Logging Library & LISP

image image image image

qxsl is a Java library for processing amateur-radio log files, including scoring and tabulation frameworks for ham radio contests, which are important components of Automatic Acceptance & Tabulation System (ATS) for the ALLJA1 contest.


Sample Codes

Because we are Scalalians, please be patient to read Scala codes!

Document Model

The package qxsl.model defines the structure of log files, where each communication is handled as an Item object, while the entire log is represented as List[Item]. Each Item contains some Field objects, which indicate properties such as Time, Mode and Band. In addition, each Item holds two Exch objects, namely Rcvd and Sent, which involve some messages (Fields) exchanged by the operator and the contacted station.

import qxsl.model.{Item,Rcvd,Sent}
val item: Item = new Item
val rcvd: Rcvd = item.getRcvd
val sent: Sent = item.getSent

Field Management

The package qxsl.field provides a management framework for Field implementations. The class FieldFormats detects FieldFormat implementations from the class path automatically, and each FieldFormat provides en/decoders for individual Field implementation.

val fmts = new qxsl.field.FieldFormats
item.add(fmts.cache(new QName("qxsl.org", "mode")).field("CW"))
item.add(fmts.cache(new QName("adif.org", "MODE")).field("CW"))
val mode = item.get(new QName("qxsl.org", "mode")).value()

This mechanism is utilized for en/decoding the QXML format, which is an alternative log format proposed by the qxsl development team. QXML is extensible, and supports namespaces which have been prohibited in the traditional ADIF:

<?xml version="1.0" encoding="UTF-8"?>
<list xmlns:qxsl="qxsl.org">
  <item qxsl:time="2017-06-03T16:17:00Z" qxsl:call="QV1DOK" qxsl:band="14000" qxsl:mode="CW">
    <rcvd qxsl:rstq="599" qxsl:code="120103"/>
    <sent qxsl:rstq="599" qxsl:code="100110"/>
  <item qxsl:time="2017-06-04T00:01:00Z" qxsl:call="QD1QXB" qxsl:band="21000" qxsl:mode="CW">
    <rcvd qxsl:rstq="599" qxsl:code="110117"/>
    <sent qxsl:rstq="599" qxsl:code="100110"/>

Decoding & Encoding

The package qxsl.table provides a basic framework for en/decoding log files including QXML and ADIF. The class TableFormats detects individual formats (TableFormats) from the class path automatically, and also provides the detect method for automatic format detection.

val fmts = new qxsl.table.TableFormats()
val table: List[Item] = fmts.decode(Files.newInputStream(path))

The package qxsl.sheet provides an en/decoding framework similar to the qxsl.table package, except that qxsl.sheet handles contest summary sheets such as Cabrillo and JARL summary sheet R2.0. The class SheetFormats manages individual SheetFormat implementations, and also provides the unpack method useful for extracting List[Item] from a summary sheet.

val fmts = new qxsl.sheet.SheetFormats()
val table: List[Item] = fmts.unpack(Files.newBufferedReader(path))

Scoring for Awards & Contests

The package qxsl.ruler provides a rulemaking framework for amateur radio awards and contests. Each contest is represented as a Contest object, which involves multiple Section objects. The Section object accepts List[Item] and validates the communications one by one, by invoking the summarize method. The class RuleKit provides a LISP engine optimized for this process.

import qxsl.ruler.{Contest,RuleKit,Section,Summary}

val contest: Contest = RuleKit.load("elva").contest("""
(load "qxsl/ruler/radial.lisp")
(defmacro SUCCESS (tests) (lambda it (success it 1 (qxsl-call it))))
(defmacro scoring (score calls mults) `(* score (length ',mults)))
(setq test (contest "CQ AWESOME CONTEST" scoring))
(SECTION test "CW 14MHz Single OP" "SinCW14" (SUCCESS (CW? 14MHz?)))
(SECTION test "CW 21MHz Single OP" "SinCW21" (SUCCESS (CW? 21MHz?)))
(SECTION test "CW 28MHz Single OP" "SinCW28" (SUCCESS (CW? 28MHz?)))
(SECTION test "PH 14MHz Single OP" "SinPH14" (SUCCESS (PH? 14MHz?)))
(SECTION test "PH 21MHz Single OP" "SinPH21" (SUCCESS (PH? 21MHz?)))
(SECTION test "PH 28MHz Single OP" "SinPH28" (SUCCESS (PH? 28MHz?)))""")

val section: Section = contest.getSection("CW 14MHz SINGLE-OP")
val summary: Summary = section.summarize(table)

The original LISP engine is provided by the package elva.

Bundled Contest Definitions

The following LISP programs are bundled inside the JAR file, as sample codes in Elva Lisp.



If you want to use the latest build, configure the build.gradle as follows:

repositories.maven {

dependencies {


Gradle retrieves dependent libraries, runs tests, and generates a JAR file automatically.

$ gradle build javadoc publish

You can create a native library instead of a JAR file. GraalVM must be installed before compilation. Then run the command manually as follows:

$ native-image --shared -cp build/libs/qxsl.jar -H:Name=qxsl

You must implement entry points by your self.


Feel free to contact @nextzlog on Twitter.