///|
pub struct DataLine {
number : Int
text : String
}
///|
pub struct LasHeader {
curves : Array[Curve]
well : Array[Header]
parameters : Array[Header]
other : Array[String]
version_headers : Array[Header]
extra_sections : Array[LasSection]
data : Array[DataLine]
wrapped : Bool
null_value : Double
}
///|
fn header_value(
headers : Array[Header],
name : String,
) -> Header raise WellError {
for h in headers {
if canonical(h.name) == name {
return h
}
}
fail("missing required header: " + name)
Header::{ name, unit: "", value: "", description: "", }
}
///|
fn insert_header(headers : Array[Header], h : Header) -> Unit raise WellError {
for old in headers {
if canonical(old.name) == canonical(h.name) {
fail("duplicate header: " + h.name)
}
}
headers.push(h)
}
///|
/// Parse the bounded section state machine. No data is decoded at this stage.
pub fn read_header(text : String) -> LasHeader raise WellError {
if text.length() > 16000000 {
fail("input exceeds 16000000 code units")
}
let version : Array[Header] = []
let well : Array[Header] = []
let parameters : Array[Header] = []
let curves : Array[Curve] = []
let other : Array[String] = []
let data : Array[DataLine] = []
let extras : Array[LasSection] = []
let seen : Array[String] = []
let mut section = ""
let mut number = 0
for raw in text.split("\n") {
number += 1
let line = raw.trim().to_owned()
if line == "" {
continue
}
if line.has_prefix("~") {
let tag = canonical(line)
let next = if tag.has_prefix("~V") {
"V"
} else if tag.has_prefix("~W") {
"W"
} else if tag.has_prefix("~C") {
"C"
} else if tag.has_prefix("~P") {
"P"
} else if tag.has_prefix("~O") {
"O"
} else if tag.has_prefix("~A") {
"A"
} else {
"X"
}
if section == "A" {
fail("sections after ASCII are unsupported")
}
if seen.length() == 0 && next != "V" {
fail("Version must be the first section")
}
if next != "X" && seen.contains(next) {
fail("duplicate section: " + next)
}
if next == "A" &&
(!seen.contains("V") || !seen.contains("W") || !seen.contains("C")) {
fail("Version, Well and Curve sections must precede ASCII")
}
if next == "X" {
extras.push({ title: line, lines: [], })
}
seen.push(next)
section = next
continue
}
if section == "X" {
extras[extras.length() - 1].lines.push(raw.trim_end().to_owned())
continue
}
if line.has_prefix("#") {
continue
}
if section == "A" {
data.push({ number, text: line, })
continue
}
if section == "O" {
other.push(line)
continue
}
if section == "" {
fail("content before section at line " + number.to_string())
}
let h = parse_header(line) catch {
WellError(message) => {
fail("line " + number.to_string() + ": " + message)
Header::{ name: "", unit: "", value: "", description: "", }
}
}
match section {
"V" => insert_header(version, h)
"W" => insert_header(well, h)
"P" => insert_header(parameters, h)
"C" => {
curves.push({
name: h.name,
unit: h.unit,
description: h.description,
api_code: h.value,
})
if curves.length() > 512 {
fail("curve limit is 512")
}
}
_ => fail("invalid parser state")
}
}
if !seen.contains("A") {
fail("missing ASCII section")
}
if parse_number(header_value(version, "VERS").value) != 2.0 {
fail("only LAS 2.0 is supported")
}
let wrap = canonical(header_value(version, "WRAP").value)
if wrap != "YES" && wrap != "NO" {
fail("WRAP must be YES or NO")
}
if curves.length() == 0 {
fail("Curve section is empty")
}
for name in ["STRT", "STOP", "STEP"] {
let h = header_value(well, name)
ignore(parse_number(h.value))
if canonical(h.unit) != canonical(curves[0].unit) {
fail(name + " unit differs from depth curve")
}
}
let null_value = parse_number(header_value(well, "NULL").value)
LasHeader::{
curves,
version_headers: version.filter(h => {
let key = canonical(h.name)
key != "VERS" && key != "WRAP"
}),
extra_sections: extras,
well,
parameters,
other,
data,
wrapped: wrap == "YES",
null_value,
}
}