Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conform to CFITSIO hierarch specification #159

Merged
merged 2 commits into from Sep 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/main/java/nom/tam/fits/HeaderCard.java
Expand Up @@ -199,7 +199,7 @@ public class HeaderCard implements CursorValue<String> {
* <li>If {@link FitsFactory#setUseHierarch(boolean)} is enabled, structured longer keywords can be composed
* after a <code>HIERARCH</code> base key, followed by space (and/or dot ].]) separated parts, up to an
* equal sign [=]. The library will represent the same components (including <code>HIERARCH</code>) but
* separated by single dots [.]. For example, the header line starting with [<code>HIERARCH SMA OBS TARGET=</code>],
* separated by single dots [.]. For example, the header line starting with [<code>HIERARCH SMA OBS TARGET =</code>],
* will be referred as [<code>HIERARCH.SMA.OBS.TARGET</code>] withing this library. The keyword parts
* can be composed of any ASCII characters except dot [.], white spaces, or equal [=].</li>
* <li>By default, all parts of the key are converted to upper-case. Case sensitive HIERARCH keywords can be
Expand Down Expand Up @@ -1375,7 +1375,11 @@ private int maxStringValueLength() {

private boolean stringValueToString(int alignSmallString, int alignPosition, FitsLineAppender buf, boolean commentHandled) {
String stringValue = this.value.replace("'", "''");
if (FitsFactory.isLongStringsEnabled() && stringValue.length() > maxStringValueLength()) {

// We can only write a single-line string, including the quotes, in the space left on the line...
int spaceLeft = FITS_HEADER_CARD_SIZE - buf.length() % FITS_HEADER_CARD_SIZE - 2;

if (FitsFactory.isLongStringsEnabled() && stringValue.length() > spaceLeft) {
writeLongStringValue(buf, stringValue);
commentHandled = true;
} else {
Expand Down Expand Up @@ -1459,7 +1463,8 @@ void setKey(String newKey) {

/**
* A helper utility class to parse header cards for there value (especially
* strings) and comments.
* strings) and comments. See {@link HeaderCard#create(String)} for a description
* of the rules that guide parsing.
*
* @author Attila Kovacs
* @author Richard van Nieuwenhoven
Expand Down Expand Up @@ -1826,7 +1831,7 @@ private void parseStringValue() throws IllegalArgumentException {
} else {
value = null;
parsePos = from;
throw new IllegalArgumentException("Missing or unexpected single quotes in value");
throw new IllegalArgumentException("Missing end quote in [" + line + "]");
}
}
}
Expand Down
Expand Up @@ -62,7 +62,9 @@ public void append(String key, FitsLineAppender buffer) {

buffer.append(HIERARCH_TEXT);
buffer.append(blanks);
buffer.append(key, HIERARCH_KEY_OFFSET, key.length());
buffer.append(key, HIERARCH_KEY_OFFSET, key.length());
// cfitsio specifies a required space before the '=', so let's play nice with it.
buffer.append(' ');
}

@Override
Expand Down
Expand Up @@ -44,6 +44,8 @@ public void append(String key, FitsLineAppender buffer) {
key = key.toUpperCase(Locale.US);
}
buffer.appendReplacing(key, '.', ' ');
// cfitsio specifies a required space before the '=', so let's play nice with it.
buffer.append(' ');
}

@Override
Expand Down
33 changes: 17 additions & 16 deletions src/test/java/nom/tam/fits/test/HeaderCardTest.java
Expand Up @@ -446,11 +446,11 @@ public void testHierarchFormatting() throws Exception {
FitsFactory.setUseHierarch(true);
HeaderCard hc;
hc = new HeaderCard("HIERARCH.TEST1.INT", "xx", "Comment");
assertEquals("HIERARCH TEST1 INT= 'xx' / Comment ", hc.toString());
assertEquals("HIERARCH TEST1 INT = 'xx' / Comment ", hc.toString());
hc = new HeaderCard("HIERARCH.TEST1.TEST2.INT", "xx", "Comment");
assertEquals("HIERARCH TEST1 TEST2 INT= 'xx' / Comment ", hc.toString());
assertEquals("HIERARCH TEST1 TEST2 INT = 'xx' / Comment ", hc.toString());
hc = new HeaderCard("HIERARCH.TEST1.TEST3.B", "xx", "Comment");
assertEquals("HIERARCH TEST1 TEST3 B= 'xx' / Comment ", hc.toString());
assertEquals("HIERARCH TEST1 TEST3 B = 'xx' / Comment ", hc.toString());
}

@Test
Expand All @@ -469,6 +469,7 @@ public void testSimpleHierarch() throws Exception {
assertEquals("HIERARCH", hc.getKey());
assertEquals("0.123", hc.getValue());
assertEquals("Comment", hc.getComment());
assertEquals("HIERARCH= 0.123 / Comment", hc.toString().trim());
}

@Test
Expand Down Expand Up @@ -497,7 +498,7 @@ public void testHierarch() throws Exception {

hc = new HeaderCard(key, 123, "Comment");

assertEquals("HIERARCH TEST1 TEST2 INT= 123 / Comment", hc.toString().trim());
assertEquals("HIERARCH TEST1 TEST2 INT = 123 / Comment", hc.toString().trim());

assertEquals("h5", key, hc.getKey());
assertEquals("h6", "123", hc.getValue());
Expand All @@ -516,8 +517,8 @@ public void testHierarch() throws Exception {

hc = new HeaderCard(key, "a verly long value that must be splitted over multiple lines to fit the card", "the comment is also not the smallest");

assertEquals("HIERARCH TEST1 TEST2 INT= 'a verly long value that must be splitted over multi&'" + //
"CONTINUE 'ple lines to fit the card' / the comment is also not the smallest ", hc.toString());
assertEquals("HIERARCH TEST1 TEST2 INT = 'a verly long value that must be splitted over mult&'" + //
"CONTINUE 'iple lines to fit the card' / the comment is also not the smallest ", hc.toString());

}

Expand Down Expand Up @@ -586,8 +587,8 @@ public void testLongStringWithSkippedBlank() throws Exception {

HeaderCard hc = new HeaderCard(key, "a verly long value that must be splitted over multiple lines to fit the card", "the comment is also not the smallest");

assertEquals("HIERARCH TEST1 TEST2 INT='a verly long value that must be splitted over multip&'" + //
"CONTINUE 'le lines to fit the card' / the comment is also not the smallest ", hc.toString());
assertEquals("HIERARCH TEST1 TEST2 INT ='a verly long value that must be splitted over multi&'" + //
"CONTINUE 'ple lines to fit the card' / the comment is also not the smallest ", hc.toString());

}

Expand Down Expand Up @@ -860,17 +861,17 @@ public void testHierarchAlternatives() throws Exception {
FitsFactory.setUseHierarch(true);
HeaderCard headerCard = new HeaderCard("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", "xy", null);
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", headerCard.getKey());
assertEquals("HIERARCH TEST1 TEST2 TEST3 TEST4 TEST5 TEST6= 'xy' ", headerCard.toString());
assertEquals("HIERARCH TEST1 TEST2 TEST3 TEST4 TEST5 TEST6 = 'xy' ", headerCard.toString());
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", new HeaderCard(headerCardToStream(headerCard)).getKey());

FitsFactory.setHierarchFormater(new BlanksDotHierarchKeyFormatter(1));
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", headerCard.getKey());
assertEquals("HIERARCH TEST1.TEST2.TEST3.TEST4.TEST5.TEST6= 'xy' ", headerCard.toString());
assertEquals("HIERARCH TEST1.TEST2.TEST3.TEST4.TEST5.TEST6 = 'xy' ", headerCard.toString());
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", new HeaderCard(headerCardToStream(headerCard)).getKey());

FitsFactory.setHierarchFormater(new BlanksDotHierarchKeyFormatter(2));
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", headerCard.getKey());
assertEquals("HIERARCH TEST1.TEST2.TEST3.TEST4.TEST5.TEST6= 'xy' ", headerCard.toString());
assertEquals("HIERARCH TEST1.TEST2.TEST3.TEST4.TEST5.TEST6 = 'xy' ", headerCard.toString());
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", new HeaderCard(headerCardToStream(headerCard)).getKey());

}
Expand Down Expand Up @@ -901,17 +902,17 @@ public void testHierarchAlternativesWithSkippedBlank() throws Exception {
FitsFactory.setUseHierarch(true);
HeaderCard headerCard = new HeaderCard("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", "xy", null);
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", headerCard.getKey());
assertEquals("HIERARCH TEST1 TEST2 TEST3 TEST4 TEST5 TEST6='xy' ", headerCard.toString());
assertEquals("HIERARCH TEST1 TEST2 TEST3 TEST4 TEST5 TEST6 ='xy' ", headerCard.toString());
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", new HeaderCard(headerCardToStream(headerCard)).getKey());

FitsFactory.setHierarchFormater(new BlanksDotHierarchKeyFormatter(1));
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", headerCard.getKey());
assertEquals("HIERARCH TEST1.TEST2.TEST3.TEST4.TEST5.TEST6='xy' ", headerCard.toString());
assertEquals("HIERARCH TEST1.TEST2.TEST3.TEST4.TEST5.TEST6 ='xy' ", headerCard.toString());
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", new HeaderCard(headerCardToStream(headerCard)).getKey());

FitsFactory.setHierarchFormater(new BlanksDotHierarchKeyFormatter(2));
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", headerCard.getKey());
assertEquals("HIERARCH TEST1.TEST2.TEST3.TEST4.TEST5.TEST6='xy' ", headerCard.toString());
assertEquals("HIERARCH TEST1.TEST2.TEST3.TEST4.TEST5.TEST6 ='xy' ", headerCard.toString());
assertEquals("HIERARCH.TEST1.TEST2.TEST3.TEST4.TEST5.TEST6", new HeaderCard(headerCardToStream(headerCard)).getKey());

}
Expand Down Expand Up @@ -1024,12 +1025,12 @@ public void testHeaderCardFormatHierarch() throws Exception {
assertEquals("UTC", card.getValue());
assertEquals("All dates are in UTC time", card.getComment());
assertEquals("HIERARCH.TIMESYS.BBBB.CCCC", card.getKey());
assertEquals("HIERARCH TIMESYS BBBB CCCC='UTC' / All dates are in UTC time ", card.toString());
assertEquals("HIERARCH TIMESYS BBBB CCCC ='UTC' / All dates are in UTC time ", card.toString());

card = HeaderCard.create("HIERARCH TIMESYS.BBBB.CCCC ='UTC ' / All dates are in UTC time");
assertEquals("UTC", card.getValue());
assertEquals("All dates are in UTC time", card.getComment());
assertEquals("HIERARCH.TIMESYS.BBBB.CCCC", card.getKey());
assertEquals("HIERARCH TIMESYS BBBB CCCC='UTC' / All dates are in UTC time ", card.toString());
assertEquals("HIERARCH TIMESYS BBBB CCCC ='UTC' / All dates are in UTC time ", card.toString());
}
}
36 changes: 19 additions & 17 deletions src/test/java/nom/tam/fits/test/HeaderTest.java
Expand Up @@ -171,7 +171,7 @@ public void cursorTest() throws Exception {
hdr.addValue("FLT1", 1.34, "A float value");
hdr.addValue("FLT2", -1.234567890e-134, "A very long float");
hdr.insertComment("Comment after flt2");

c.setKey("INTVAL1");
hc = (HeaderCard) c.next();
assertEquals("INTVAL1", "INTVAL1", hc.getKey());
Expand Down Expand Up @@ -204,8 +204,8 @@ public void cursorTest() throws Exception {
SafeClose.close(f);
}
}


/** Confirm initial location versus EXTEND keyword (V. Forchi). */
@Test
public void extendTest() throws Exception {
Expand Down Expand Up @@ -366,7 +366,7 @@ public void longStringNullComment() throws Exception {
assertNull(card.getComment());
assertFalse(new HeaderCard("STRKEY",
"This is a very long string keyword value that is continued over at least three keywords in the FITS header even if it has no comment.", null).toString()
.contains("/"));
.contains("/"));
FitsFactory.setLongStringsEnabled(false);
}

Expand Down Expand Up @@ -411,7 +411,7 @@ private void checkOneCombination(String cardValue, String cardComment) throws Ex
Arrays.fill(bytes, (byte) ' ');
System.arraycopy(AsciiFuncs.getBytes(cardString), 0, bytes, 0, cardString.length());
HeaderCard rereadCard = new HeaderCard(new BufferedDataInputStream(new ByteArrayInputStream(bytes)));

assertEquals(cardValue, rereadCard.getValue());
assertEquals(headerCard.getValue(), rereadCard.getValue());
assertEquals(cardComment, headerCard.getComment());
Expand Down Expand Up @@ -487,7 +487,7 @@ public void testBadHeader() throws Exception {
SafeClose.close(f);
}
}


@Test
public void testHeaderCommentsDrift() throws Exception {
Expand Down Expand Up @@ -530,7 +530,7 @@ public void testHeaderCommentsDrift() throws Exception {
@Test
public void testHierachKeyWordParsing() {
FitsFactory.setUseHierarch(true);

String keyword = HeaderCard.create("HIERARCH test this = 'bla bla' ").getKey();
assertEquals("HIERARCH.TEST.THIS", keyword);

Expand Down Expand Up @@ -657,11 +657,11 @@ public void testUpdateHeaderComments() throws Exception {
SafeClose.close(f);
}
}

@Test
public void orderTest() throws Exception {
Header h = new Header();

// Start with an 'existing' header
h.addValue("BLAH1", 0, "");
h.addValue("BLAH2", 0, "");
Expand All @@ -670,8 +670,8 @@ public void orderTest() throws Exception {
h.addValue("MARKER", 0, "");
h.addValue("BLAH4", 0, "");
h.addValue("KEY2", 0, "");


// Now insert some keys before MARKER
h.findCard("MARKER");
h.addValue("KEY1", 1, "");
Expand All @@ -680,21 +680,21 @@ public void orderTest() throws Exception {
h.addValue("KEY4", 1, "");
h.addValue("KEY5", 1, "");


// Now do an in-place update and a deletion, which should not affect
// the position. The position should remain before MARKER.
h.updateLine("BLAH2", new HeaderCard("BLAH2A", 1, ""));
h.deleteKey("BLAH1");
h.addValue("KEY6", 1, "");

// Use updateLine for a non-existent key. This should result in
// adding a new card at the current position
h.updateLine("KEY7", new HeaderCard("KEY7", 1, ""));

// Check to that the keys appear in the expected order...
Cursor<String, HeaderCard> c = h.iterator();
c.setKey("KEY1");

for(int i=1; i<=7; i++) {
HeaderCard card = c.next();
assertEquals("KEY" + i, card.getKey());
Expand Down Expand Up @@ -747,7 +747,7 @@ public void addValueTests() throws Exception {
assertEquals(hdr.getDoubleValue(CTYPE2.name()), 5.0, 0.000001);
assertEquals(hdr.getDoubleValue(CTYPE2), 5.0, 0.000001);
assertEquals(hdr.getDoubleValue("ZZZ", -1.0), -1.0, 0.000001);

hdr.addValue(CTYPE2.name(), BigDecimal.valueOf(5.0), "nothing special");
assertEquals(hdr.getDoubleValue(CTYPE2.name()), 5.0, 0.000001);
assertEquals(hdr.getDoubleValue(CTYPE2, -1d), 5.0, 0.000001);
Expand Down Expand Up @@ -879,7 +879,7 @@ private Header invalidHeaderTests() throws HeaderCardException {
iterator.add(new HeaderCard(NAXIS.name(), 1, ""));
Assert.assertEquals(0, header.getSize());
header.addValue("END", "", "");

Assert.assertEquals(2880, header.getSize());
return header;
}
Expand Down Expand Up @@ -980,7 +980,9 @@ public void testHierarchLongStringIssue44() throws Exception {
*/
try {
f = new Fits(filename);

Header headerRewriter = f.getHDU(0).getHeader();

assertEquals("aaaaaaaabbbbbbbbbcccccccccccdddddddddddeeeeeeeeeee", headerRewriter.findCard("HIERARCH.TEST.THIS.LONG.HEADER").getValue());
for (int index = 1; index < 60; index++) {
StringBuilder buildder = new StringBuilder();
Expand Down