use super::misc::mktag;
use crate::error::{Error, Result};
use crate::tag::Tag;
use crate::value::Value;
pub fn read_fits(data: &[u8]) -> Result<Vec<Tag>> {
if data.len() < 80 || !data.starts_with(b"SIMPLE =") {
return Err(Error::InvalidData("not a FITS file".into()));
}
let mut tags = Vec::new();
let mut pos = 0;
let mut continue_tag: Option<String> = None;
let mut continue_val: String = String::new();
while pos + 80 <= data.len() {
let record = &data[pos..pos + 80];
let keyword = crate::encoding::decode_utf8_or_latin1(&record[..8])
.trim_end()
.to_string();
pos += 80;
if keyword == "END" {
break;
}
if keyword == "CONTINUE" {
if continue_tag.is_some() {
let val_raw = crate::encoding::decode_utf8_or_latin1(&record[8..]).to_string();
let (more, cont) = fits_parse_continued_value(&val_raw);
continue_val.push_str(&more);
if !cont {
let tag_name = continue_tag.take().unwrap();
let tag_desc = fits_tag_description(&tag_name);
tags.push(mktag(
"FITS",
&tag_name,
&tag_desc,
Value::String(continue_val.clone()),
));
continue_val.clear();
}
}
continue;
}
if let Some(tag_name) = continue_tag.take() {
let tag_desc = fits_tag_description(&tag_name);
tags.push(mktag(
"FITS",
&tag_name,
&tag_desc,
Value::String(continue_val.clone()),
));
continue_val.clear();
}
if keyword == "COMMENT" || keyword == "HISTORY" {
let val = crate::encoding::decode_utf8_or_latin1(&record[8..])
.trim_end()
.to_string();
let name = if keyword == "COMMENT" {
"Comment"
} else {
"History"
};
tags.push(mktag("FITS", name, name, Value::String(val)));
continue;
}
if keyword.is_empty() {
continue;
}
if record.len() <= 10 || record[8] != b'=' {
continue;
}
let val_raw = crate::encoding::decode_utf8_or_latin1(&record[10..]).to_string();
let (value, is_continued) = fits_parse_value(&val_raw);
if value.is_empty() {
continue;
}
let tag_name = fits_keyword_to_name(&keyword);
let tag_desc = fits_tag_description(&tag_name);
if is_continued {
continue_tag = Some(tag_name);
continue_val = value;
} else {
tags.push(mktag("FITS", &tag_name, &tag_desc, Value::String(value)));
}
}
if let Some(tag_name) = continue_tag.take() {
let tag_desc = fits_tag_description(&tag_name);
tags.push(mktag(
"FITS",
&tag_name,
&tag_desc,
Value::String(continue_val.clone()),
));
}
Ok(tags)
}
fn fits_parse_value(s: &str) -> (String, bool) {
let s = s.trim_start();
if let Some(inner) = s.strip_prefix('\'') {
let mut result = String::new();
let mut chars = inner.chars().peekable();
loop {
match chars.next() {
None => break,
Some('\'') => {
if chars.peek() == Some(&'\'') {
chars.next();
result.push('\'');
} else {
break; }
}
Some(c) => result.push(c),
}
}
let trimmed = result.trim_end().to_string();
let is_cont = trimmed.ends_with('&');
let val = if is_cont {
trimmed[..trimmed.len() - 1].to_string()
} else {
trimmed
};
(val, is_cont)
} else {
let val = s.split('/').next().unwrap_or("").trim().to_string();
let val = val.replace(['D', 'E'], "e");
if val.is_empty() {
return (String::new(), false);
}
(val, false)
}
}
fn fits_parse_continued_value(s: &str) -> (String, bool) {
fits_parse_value(s)
}
fn fits_keyword_to_name(keyword: &str) -> String {
match keyword {
"SIMPLE" => String::new(), "BITPIX" => "Bitpix".into(),
"NAXIS" => "Naxis".into(),
"NAXIS1" => "Naxis1".into(),
"NAXIS2" => "Naxis2".into(),
"EXTEND" => "Extend".into(),
"ORIGIN" => "Origin".into(),
"TELESCOP" => "Telescope".into(),
"BACKGRND" => "Background".into(),
"INSTRUME" => "Instrument".into(),
"OBJECT" => "Object".into(),
"OBSERVER" => "Observer".into(),
"DATE" => "CreateDate".into(),
"AUTHOR" => "Creator".into(),
"REFERENC" => "Reference".into(),
"DATE-OBS" => "ObservationDate".into(),
"TIME-OBS" => "ObservationTime".into(),
"DATE-END" => "ObservationDateEnd".into(),
"TIME-END" => "ObservationTimeEnd".into(),
"COMMENT" => "Comment".into(),
"HISTORY" => "History".into(),
_ => {
let lower = keyword.to_lowercase();
let mut result = String::new();
let mut capitalize_next = true;
for ch in lower.chars() {
if ch == '_' || ch == '-' {
capitalize_next = true;
} else if capitalize_next {
for c in ch.to_uppercase() {
result.push(c);
}
capitalize_next = false;
} else {
result.push(ch);
}
}
result
}
}
}
fn fits_tag_description(name: &str) -> String {
let mut desc = String::new();
for ch in name.chars() {
if ch.is_uppercase() && !desc.is_empty() {
desc.push(' ');
}
desc.push(ch);
}
desc
}