Task 326: .JDX File Format
Task 326: .IDX File Format
1. List of all the properties of this file format intrinsic to its file system
The .JDX file format is the JCAMP-DX standard, a text-based format for exchanging spectral data (primarily infrared, but extensible to other spectroscopy types). It consists of Label-Data Records (LDRs), which are key-value pairs starting with ##
followed by a label, an =
, and the value. These LDRs represent the intrinsic properties of the file. Values can be text, strings, numeric (AFFN: ASCII Free Format Numeric), or compressed data (ASDF: ASCII Squeezed Difference Form for spectral data). Files are ASCII-encoded, with lines up to 80 characters, and can be simple (single block) or compound (multiple blocks). Blocks start with ##TITLE=
and end with ##END=
. Comments start with $$
. User-defined labels start with $
.
Based on the JCAMP-DX Version 4.24 specification (core for infrared spectra, with extensions possible for other types), the standard properties (LDR labels) are grouped as follows:
Core Properties (Irreducible minimum for a valid file)
##TITLE
: Concise description of the spectrum (required first LDR).##JCAMP-DX
: Version of the JCAMP-DX format (e.g., 4.24; required).##DATA TYPE
: Type of data in the block (e.g., INFRARED SPECTRUM; required).##BLOCKS
: Number of link and data blocks in compound files (required in link blocks).##END
: Marks the end of a block (required).##XUNITS
: Abscissa units (e.g., 1/CM).##YUNITS
: Ordinate units (e.g., TRANSMITTANCE).##FIRSTX
: First actual X-value.##LASTX
: Last actual X-value.##XFACTOR
: Scaling factor for X-values.##YFACTOR
: Scaling factor for Y-values.##NPOINTS
: Number of data points.##FIRSTY
: First actual Y-value.##XYDATA
: Tabular spectral data (in AFFN or ASDF format).##MAXX
: Largest actual X-value (optional for scaling).##MINX
: Smallest actual X-value (optional).##MAXY
: Largest actual Y-value (optional).##MINY
: Smallest actual Y-value (optional).
Required Header Properties (For pure compound spectra)
##CLASS
: Coblentz and IUPAC class of the spectrum.##ORIGIN
: Originator's name, address, etc.##OWNER
: Owner of the spectrum (e.g., copyright info).
Optional Notes Properties (Additional metadata)
##DATE
: Date spectrum was measured (YY/MM/DD).##TIME
: Time spectrum was measured (HH:MM:SS).##SOURCE REFERENCE
: Reference to original spectrum source.##CROSS REFERENCE
: Links to related spectra.##SAMPLE DESCRIPTION
: Description of the sample.##CAS NAME
: Chemical Abstracts name.##NAMES
: Common or trade names.##MOLFORM
: Molecular formula.##CAS REGISTRY NO
: CAS number.##WISWESSER
: Wiswesser line notation.##BEILSTEIN LAWSON NO
: Beilstein number.##MP
: Melting point (°C).##BP
: Boiling point (°C).##REFRACTIVE INDEX
: Refractive index.##DENSITY
: Density (g/cc).##MW
: Molecular weight.##CONCENTRATIONS
: Component concentrations.##SPECTROMETER/DATA SYSTEM
: Spectrometer and software details.##INSTRUMENT PARAMETERS
: Instrumental settings.##SAMPLING PROCEDURE
: Sampling method.##STATE
: Physical state of sample.##PATH LENGTH
: Path length (cm).##PRESSURE
: Sample pressure.##TEMPERATURE
: Sample temperature (°C).##DATA PROCESSING
: Data processing details.
These properties define the file's structure and content. Extensions (e.g., for NMR in JCAMP-DX 5.0) may add more labels like ##DELTAX
, ##RESOLUTION
, but the above are intrinsic to the base format.
2. Two direct download links for files of format .JDX
- https://webbook.nist.gov/cgi/cbook.cgi?JCAMP=C67641&Index=1&Type=IR (NIST IR spectrum for acetone)
- https://webbook.nist.gov/cgi/cbook.cgi?JCAMP=C7732185&Index=0&Type=IR (NIST IR spectrum for water)
3. Ghost blog embedded HTML JavaScript that allows a user to drag n drop a file of format .JDX and it will dump to screen all these properties
This HTML page can be embedded in a Ghost blog post. It creates a drag-and-drop zone that reads the .JDX file, parses the LDRs (handling multi-line values), and dumps the properties as JSON to the screen.
4. Python class that can open any file of format .JDX and decode read and write and print to console all the properties from the above list
import json
import os
class JDXHandler:
def __init__(self, filepath=None):
self.properties = {}
self.filepath = filepath
if filepath:
self.read(filepath)
def read(self, filepath):
"""Read and decode .JDX file, parsing properties."""
self.filepath = filepath
self.properties = {}
with open(filepath, 'r') as f:
content = f.read()
lines = content.splitlines()
current_label = None
for line in lines:
line = line.strip()
if line.startswith('##'):
parts = line.split('=', 1)
if len(parts) == 2:
current_label = parts[0].strip()[2:].replace('-', '').replace('/', '').replace(' ', '')
self.properties[current_label] = parts[1].strip()
else:
current_label = None
elif current_label and line:
self.properties[current_label] += '\n' + line
print("Properties read from file:")
self.print_properties()
def print_properties(self):
"""Print all properties to console."""
print(json.dumps(self.properties, indent=4))
def write(self, output_filepath):
"""Write properties back to a new .JDX file."""
if not self.properties:
raise ValueError("No properties to write.")
with open(output_filepath, 'w') as f:
for label, value in self.properties.items():
f.write(f"##{label.upper()}={value}\n")
f.write("##END=\n")
print(f"File written to {output_filepath}")
# Example usage:
# handler = JDXHandler('example.jdx')
# handler.write('output.jdx')
This class opens a .JDX file, decodes/parses the properties (handling multi-line), prints them as JSON, and can write a new file with the properties.
5. Java class that can open any file of format .JDX and decode read and write and print to console all the properties from the above list
import java.io.*;
import java.util.*;
public class JDXHandler {
private Map<String, String> properties = new HashMap<>();
private String filepath;
public JDXHandler(String filepath) {
this.filepath = filepath;
if (filepath != null) {
read(filepath);
}
}
public void read(String filepath) {
properties.clear();
this.filepath = filepath;
try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
String line;
String currentLabel = null;
StringBuilder valueBuilder = new StringBuilder();
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("##")) {
if (currentLabel != null) {
properties.put(currentLabel, valueBuilder.toString().trim());
}
String[] parts = line.split("=", 2);
if (parts.length == 2) {
currentLabel = parts[0].substring(2).trim().replace("-", "").replace("/", "").replace(" ", "");
valueBuilder = new StringBuilder(parts[1].trim());
} else {
currentLabel = null;
}
} else if (currentLabel != null && !line.isEmpty()) {
valueBuilder.append("\n").append(line);
}
}
if (currentLabel != null) {
properties.put(currentLabel, valueBuilder.toString().trim());
}
System.out.println("Properties read from file:");
printProperties();
} catch (IOException e) {
e.printStackTrace();
}
}
public void printProperties() {
for (Map.Entry<String, String> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String outputFilepath) {
if (properties.isEmpty()) {
throw new IllegalStateException("No properties to write.");
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilepath))) {
for (Map.Entry<String, String> entry : properties.entrySet()) {
writer.write("##" + entry.getKey().toUpperCase() + "=" + entry.getValue() + "\n");
}
writer.write("##END=\n");
System.out.println("File written to " + outputFilepath);
} catch (IOException e) {
e.printStackTrace();
}
}
// Example usage:
// public static void main(String[] args) {
// JDXHandler handler = new JDXHandler("example.jdx");
// handler.write("output.jdx");
// }
}
This Java class opens a .JDX file, decodes/parses properties (handling multi-line), prints them to console, and writes a new file.
6. Javascript class that can open any file of format .JDX and decode read and write and print to console all the properties from the above list
const fs = require('fs'); // For Node.js environment
class JDXHandler {
constructor(filepath = null) {
this.properties = {};
this.filepath = filepath;
if (filepath) {
this.read(filepath);
}
}
read(filepath) {
this.filepath = filepath;
this.properties = {};
const content = fs.readFileSync(filepath, 'utf8');
const lines = content.split(/\r?\n/);
let currentLabel = null;
let valueBuilder = '';
lines.forEach(line => {
line = line.trim();
if (line.startsWith('##')) {
if (currentLabel) {
this.properties[currentLabel] = valueBuilder.trim();
}
const parts = line.split('=');
if (parts.length >= 2) {
currentLabel = parts[0].trim().slice(2).replace(/[-\/ ]/g, '');
valueBuilder = parts.slice(1).join('=').trim();
} else {
currentLabel = null;
}
} else if (currentLabel && line) {
valueBuilder += '\n' + line;
}
});
if (currentLabel) {
this.properties[currentLabel] = valueBuilder.trim();
}
console.log('Properties read from file:');
this.printProperties();
}
printProperties() {
console.log(JSON.stringify(this.properties, null, 2));
}
write(outputFilepath) {
if (Object.keys(this.properties).length === 0) {
throw new Error('No properties to write.');
}
let content = '';
for (const [label, value] of Object.entries(this.properties)) {
content += `##${label.toUpperCase()}=${value}\n`;
}
content += '##END=\n';
fs.writeFileSync(outputFilepath, content);
console.log(`File written to ${outputFilepath}`);
}
}
// Example usage:
// const handler = new JDXHandler('example.jdx');
// handler.write('output.jdx');
This JavaScript class (Node.js compatible) opens a .JDX file, decodes/parses properties, prints them as JSON to console, and writes a new file.
7. C class that can open any file of format .JDX and decode read and write and print to console all the properties from the above list
In C, we use structs and functions instead of classes. Here's an equivalent implementation using a struct.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_LABELS 100
#define MAX_LABEL_LEN 64
#define MAX_VALUE_LEN 1024
typedef struct {
char labels[MAX_LABELS][MAX_LABEL_LEN];
char values[MAX_LABELS][MAX_VALUE_LEN];
int count;
char *filepath;
} JDXHandler;
void init_handler(JDXHandler *handler, const char *filepath) {
handler->count = 0;
handler->filepath = strdup(filepath);
if (filepath) {
read_jdx(handler, filepath);
}
}
void read_jdx(JDXHandler *handler, const char *filepath) {
FILE *file = fopen(filepath, "r");
if (!file) {
perror("Error opening file");
return;
}
handler->count = 0;
char line[256];
char current_label[MAX_LABEL_LEN] = {0};
char value_builder[MAX_VALUE_LEN] = {0};
while (fgets(line, sizeof(line), file)) {
char *trimmed = line;
while (isspace(*trimmed)) trimmed++;
if (strncmp(trimmed, "##", 2) == 0) {
if (strlen(current_label) > 0) {
strcpy(handler->values[handler->count], value_builder);
handler->count++;
}
char *eq_pos = strchr(trimmed + 2, '=');
if (eq_pos) {
int label_len = eq_pos - (trimmed + 2);
strncpy(current_label, trimmed + 2, label_len);
current_label[label_len] = '\0';
// Normalize label: remove -, /, spaces
char *p = current_label;
while (*p) {
if (*p == '-' || *p == '/' || *p == ' ') {
memmove(p, p + 1, strlen(p));
} else {
p++;
}
}
strcpy(handler->labels[handler->count], current_label);
strcpy(value_builder, eq_pos + 1);
char *end = value_builder + strlen(value_builder) - 1;
while (end > value_builder && isspace(*end)) *end-- = '\0';
} else {
current_label[0] = '\0';
}
} else if (strlen(current_label) > 0 && strlen(trimmed) > 0) {
strcat(value_builder, "\n");
strcat(value_builder, trimmed);
}
}
if (strlen(current_label) > 0) {
strcpy(handler->values[handler->count], value_builder);
handler->count++;
}
fclose(file);
printf("Properties read from file:\n");
print_properties(handler);
}
void print_properties(const JDXHandler *handler) {
for (int i = 0; i < handler->count; i++) {
printf("%s: %s\n", handler->labels[i], handler->values[i]);
}
}
void write_jdx(const JDXHandler *handler, const char *output_filepath) {
if (handler->count == 0) {
fprintf(stderr, "No properties to write.\n");
return;
}
FILE *file = fopen(output_filepath, "w");
if (!file) {
perror("Error opening file for write");
return;
}
for (int i = 0; i < handler->count; i++) {
fprintf(file, "##%s=%s\n", handler->labels[i], handler->values[i]);
}
fprintf(file, "##END=\n");
fclose(file);
printf("File written to %s\n", output_filepath);
}
void free_handler(JDXHandler *handler) {
free(handler->filepath);
}
// Example usage:
// int main() {
// JDXHandler handler;
// init_handler(&handler, "example.jdx");
// write_jdx(&handler, "output.jdx");
// free_handler(&handler);
// return 0;
// }
This C implementation uses a struct to hold properties, opens/decodes a .JDX file, prints properties to console, and writes a new file.