Task 588: .QLC File Format

Task 588: .QLC File Format

  1. List of all the properties of this file format intrinsic to its file system:
  • XREF: Optional flag to generate cross-reference listing (boolean presence).
  • PORT: Port number (integer, must be 0), controller type (string: AB_LAN, MITSUBISHI, MODBUS, R_Net), baud rate (integer: 110-19200), parity (string: NONE, EVEN), low level timeout (integer: 1-50 seconds), number of ENQs (integer: 1-10), number of NAKs (integer: 1-10), app layer timeout (integer: 1-50 seconds), number of stop bits (string: STOP1, STOP2), protocol type (string: RTU, ASCII), configuration (string: CONFIG1, CONFIG2, CONFIG3), configuration parameters (node/gateway details for R_Net).
  • TYPE: PLC type (string: PLC, PLC_2, PLC_3, PLC_4, PLC_5 for Allen-Bradley; 84/184/384/484/584/884/984/EPCC for Modicon), station number (integer: 8-63 or 72-79 for Allen-Bradley, 1-247 for Modicon).
  • STATION: Station number (integer: 0-31 for Mitsubishi, 0-31 for Reliance), PC number (integer: 0-64, usually 255 for Mitsubishi).
  • I/O Statements (IN, OUT, IN_E, OUT_E): Command type (string), conversion type (string: BCD1-4 for Allen-Bradley, optional), PC address (string, format varies by controller e.g., PC00001, HR40001, R0000), offset list (list of strings: A/D/G prefix + number [array size], DUMMY_A, DUMMY_D), slot number (integer for Reliance, usually 0).
  • Comments: Lines starting with * (string text).
  • General: Uppercase text, free-form spacing, max 3500 items (commands + values), no overlapping offsets, image table offsets 0-2047 (16-bit words).

Two direct download links for files of format .QLC:
I was unable to find direct download links for .QLC files through web searches. The format is associated with an old hardware interface (Westinghouse QLC card), and no public samples are available. You can create a .QLC file using the sample source from the specification document, such as the example in Figure 4-7.

Ghost blog embedded html javascript for drag and drop .QLC file dump:

.QLC File Dumper
Drag and drop .QLC file here
  1. Python class for .QLC file handling:
import re

class QLCFileHandler:
    def __init__(self, filename):
        self.filename = filename
        self.properties = {
            'xref': False,
            'ports': [],
            'types': [],
            'stations': [],
            'ios': [],
            'comments': []
        }

    def read_and_decode(self):
        with open(self.filename, 'r') as f:
            content = f.read().upper()
            lines = content.split('\n')
            for line in lines:
                line = line.strip()
                if not line:
                    continue
                if line.startswith('*'):
                    self.properties['comments'].append(line[1:].strip())
                elif line.startswith('XREF'):
                    self.properties['xref'] = True
                elif line.startswith('PORT'):
                    params = re.split(r'\s+', line[4:].strip())
                    self.properties['ports'].append(params)
                elif line.startswith('TYPE'):
                    params = re.split(r'\s+', line[4:].strip())
                    self.properties['types'].append(params)
                elif line.startswith('STATION'):
                    params = re.split(r'\s+', line[7:].strip())
                    self.properties['stations'].append(params)
                elif line.startswith(('IN ', 'OUT ', 'IN_E ', 'OUT_E ')):
                    params = re.split(r'\s+', line.strip())
                    self.properties['ios'].append(params)

    def print_properties(self):
        print(self.properties)

    def write(self, new_filename=None):
        if not new_filename:
            new_filename = self.filename
        with open(new_filename, 'w') as f:
            if self.properties['xref']:
                f.write('XREF\n')
            for port in self.properties['ports']:
                f.write('PORT ' + ' '.join(port) + '\n')
            for typ in self.properties['types']:
                f.write('TYPE ' + ' '.join(typ) + '\n')
            for station in self.properties['stations']:
                f.write('STATION ' + ' '.join(station) + '\n')
            for io in self.properties['ios']:
                f.write(' '.join(io) + '\n')
            for comment in self.properties['comments']:
                f.write('* ' + comment + '\n')

# Example usage
# handler = QLCFileHandler('example.qlc')
# handler.read_and_decode()
# handler.print_properties()
# handler.write('new.qlc')
  1. Java class for .QLC file handling:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

public class QLCFileHandler {
    private String filename;
    private boolean xref;
    private List<List<String>> ports;
    private List<List<String>> types;
    private List<List<String>> stations;
    private List<List<String>> ios;
    private List<String> comments;

    public QLCFileHandler(String filename) {
        this.filename = filename;
        this.xref = false;
        this.ports = new ArrayList<>();
        this.types = new ArrayList<>();
        this.stations = new ArrayList<>();
        this.ios = new ArrayList<>();
        this.comments = new ArrayList<>();
    }

    public void readAndDecode() throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim().toUpperCase();
                if (line.isEmpty()) continue;
                if (line.startsWith("*")) {
                    comments.add(line.substring(1).trim());
                } else if (line.startsWith("XREF")) {
                    xref = true;
                } else if (line.startsWith("PORT")) {
                    String[] params = Pattern.compile("\\s+").split(line.substring(4).trim());
                    ports.add(List.of(params));
                } else if (line.startsWith("TYPE")) {
                    String[] params = Pattern.compile("\\s+").split(line.substring(4).trim());
                    types.add(List.of(params));
                } else if (line.startsWith("STATION")) {
                    String[] params = Pattern.compile("\\s+").split(line.substring(7).trim());
                    stations.add(List.of(params));
                } else if (line.startsWith("IN") || line.startsWith("OUT") || line.startsWith("IN_E") || line.startsWith("OUT_E")) {
                    String[] params = Pattern.compile("\\s+").split(line.trim());
                    ios.add(List.of(params));
                }
            }
        }
    }

    public void printProperties() {
        System.out.println("XREF: " + xref);
        System.out.println("Ports: " + ports);
        System.out.println("Types: " + types);
        System.out.println("Stations: " + stations);
        System.out.println("I/Os: " + ios);
        System.out.println("Comments: " + comments);
    }

    public void write(String newFilename) throws IOException {
        if (newFilename == null) newFilename = filename;
        try (FileWriter fw = new FileWriter(newFilename)) {
            if (xref) fw.write("XREF\n");
            for (List<String> port : ports) {
                fw.write("PORT " + String.join(" ", port) + "\n");
            }
            for (List<String> typ : types) {
                fw.write("TYPE " + String.join(" ", typ) + "\n");
            }
            for (List<String> station : stations) {
                fw.write("STATION " + String.join(" ", station) + "\n");
            }
            for (List<String> io : ios) {
                fw.write(String.join(" ", io) + "\n");
            }
            for (String comment : comments) {
                fw.write("* " + comment + "\n");
            }
        }
    }

    // Example usage
    // public static void main(String[] args) throws IOException {
    //     QLCFileHandler handler = new QLCFileHandler("example.qlc");
    //     handler.readAndDecode();
    //     handler.printProperties();
    //     handler.write("new.qlc");
    // }
}
  1. Javascript class for .QLC file handling:
class QLCFileHandler {
    constructor(filename) {
        this.filename = filename;
        this.properties = {
            xref: false,
            ports: [],
            types: [],
            stations: [],
            ios: [],
            comments: []
        };
    }

    async readAndDecode() {
        // Assuming Node.js with fs module
        const fs = require('fs');
        const content = fs.readFileSync(this.filename, 'utf8').toUpperCase();
        const lines = content.split('\n').filter(line => line.trim() !== '');
        lines.forEach(line => {
            line = line.trim();
            if (line.startsWith('*')) {
                this.properties.comments.push(line.substring(1).trim());
            } else if (line.startsWith('XREF')) {
                this.properties.xref = true;
            } else if (line.startsWith('PORT')) {
                const params = line.substring(4).trim().split(/\s+/);
                this.properties.ports.push(params);
            } else if (line.startsWith('TYPE')) {
                const params = line.substring(4).trim().split(/\s+/);
                this.properties.types.push(params);
            } else if (line.startsWith('STATION')) {
                const params = line.substring(7).trim().split(/\s+/);
                this.properties.stations.push(params);
            } else if (line.startsWith('IN') || line.startsWith('OUT') || line.startsWith('IN_E') || line.startsWith('OUT_E')) {
                const params = line.split(/\s+/);
                this.properties.ios.push(params);
            }
        });
    }

    printProperties() {
        console.log(this.properties);
    }

    write(newFilename = this.filename) {
        const fs = require('fs');
        let content = '';
        if (this.properties.xref) content += 'XREF\n';
        this.properties.ports.forEach(port => content += 'PORT ' + port.join(' ') + '\n');
        this.properties.types.forEach(typ => content += 'TYPE ' + typ.join(' ') + '\n');
        this.properties.stations.forEach(station => content += 'STATION ' + station.join(' ') + '\n');
        this.properties.ios.forEach(io => content += io.join(' ') + '\n');
        this.properties.comments.forEach(comment => content += '* ' + comment + '\n');
        fs.writeFileSync(newFilename, content);
    }
}

// Example usage
// const handler = new QLCFileHandler('example.qlc');
// await handler.readAndDecode();
// handler.printProperties();
// handler.write('new.qlc');
  1. C "class" (using struct and functions) for .QLC file handling:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define MAX_LINES 1000
#define MAX_PARAMS 20
#define MAX_STR 256

typedef struct {
    int xref;
    char **ports;
    int ports_count;
    char **types;
    int types_count;
    char **stations;
    int stations_count;
    char **ios;
    int ios_count;
    char **comments;
    int comments_count;
} QLCProperties;

QLCProperties* create_properties() {
    QLCProperties* props = malloc(sizeof(QLCProperties));
    props->xref = 0;
    props->ports = NULL;
    props->ports_count = 0;
    props->types = NULL;
    props->types_count = 0;
    props->stations = NULL;
    props->stations_count = 0;
    props->ios = NULL;
    props->ios_count = 0;
    props->comments = NULL;
    props->comments_count = 0;
    return props;
}

void add_to_list(char*** list, int* count, const char* line) {
    (*count)++;
    *list = realloc(*list, (*count) * sizeof(char*));
    (*list)[*count - 1] = strdup(line);
}

void read_and_decode(const char* filename, QLCProperties* props) {
    FILE* fp = fopen(filename, "r");
    if (!fp) return;
    char line[MAX_STR];
    while (fgets(line, sizeof(line), fp)) {
        for (char* p = line; *p; p++) *p = toupper(*p);
        char* trim = strtok(line, "\n");
        if (!trim || strlen(trim) == 0) continue;
        if (trim[0] == '*') {
            add_to_list(&props->comments, &props->comments_count, trim + 1);
        } else if (strncmp(trim, "XREF", 4) == 0) {
            props->xref = 1;
        } else if (strncmp(trim, "PORT", 4) == 0) {
            add_to_list(&props->ports, &props->ports_count, trim + 4);
        } else if (strncmp(trim, "TYPE", 4) == 0) {
            add_to_list(&props->types, &props->types_count, trim + 4);
        } else if (strncmp(trim, "STATION", 7) == 0) {
            add_to_list(&props->stations, &props->stations_count, trim + 7);
        } else if (strncmp(trim, "IN", 2) == 0 || strncmp(trim, "OUT", 3) == 0 ||
                  strncmp(trim, "IN_E", 4) == 0 || strncmp(trim, "OUT_E", 5) == 0) {
            add_to_list(&props->ios, &props->ios_count, trim);
        }
    }
    fclose(fp);
}

void print_properties(QLCProperties* props) {
    printf("XREF: %d\n", props->xref);
    printf("Ports (%d):\n", props->ports_count);
    for (int i = 0; i < props->ports_count; i++) printf("%s\n", props->ports[i]);
    printf("Types (%d):\n", props->types_count);
    for (int i = 0; i < props->types_count; i++) printf("%s\n", props->types[i]);
    printf("Stations (%d):\n", props->stations_count);
    for (int i = 0; i < props->stations_count; i++) printf("%s\n", props->stations[i]);
    printf("I/Os (%d):\n", props->ios_count);
    for (int i = 0; i < props->ios_count; i++) printf("%s\n", props->ios[i]);
    printf("Comments (%d):\n", props->comments_count);
    for (int i = 0; i < props->comments_count; i++) printf("%s\n", props->comments[i]);
}

void write(const char* new_filename, QLCProperties* props) {
    FILE* fp = fopen(new_filename, "w");
    if (!fp) return;
    if (props->xref) fprintf(fp, "XREF\n");
    for (int i = 0; i < props->ports_count; i++) fprintf(fp, "PORT%s\n", props->ports[i]);
    for (int i = 0; i < props->types_count; i++) fprintf(fp, "TYPE%s\n", props->types[i]);
    for (int i = 0; i < props->stations_count; i++) fprintf(fp, "STATION%s\n", props->stations[i]);
    for (int i = 0; i < props->ios_count; i++) fprintf(fp, "%s\n", props->ios[i]);
    for (int i = 0; i < props->comments_count; i++) fprintf(fp, "* %s\n", props->comments[i]);
    fclose(fp);
}

void free_properties(QLCProperties* props) {
    for (int i = 0; i < props->ports_count; i++) free(props->ports[i]);
    free(props->ports);
    for (int i = 0; i < props->types_count; i++) free(props->types[i]);
    free(props->types);
    for (int i = 0; i < props->stations_count; i++) free(props->stations[i]);
    free(props->stations);
    for (int i = 0; i < props->ios_count; i++) free(props->ios[i]);
    free(props->ios);
    for (int i = 0; i < props->comments_count; i++) free(props->comments[i]);
    free(props->comments);
    free(props);
}

// Example usage
// int main() {
//     QLCProperties* props = create_properties();
//     read_and_decode("example.qlc", props);
//     print_properties(props);
//     write("new.qlc", props);
//     free_properties(props);
//     return 0;
// }