4 * Copyright: Thomas McGlynn 1997-1999.
5 * This code may be used for any purpose, non-commercial
6 * or commercial so long as this copyright notice is retained
7 * in the source code or included in or referred to in any
9 * Many thanks to David Glowacki (U. Wisconsin) for substantial
10 * improvements, enhancements and bug fixes -- including
13 /** This class describes methods to access and manipulate the individual
14 * cards for a FITS Header.
16 public class HeaderCard {
18 /** The keyword part of the card (set to null if there's no keyword) */
20 /** The value part of the card (set to null if there's no value) */
22 /** The comment part of the card (set to null if there's no comment) */
23 private String comment;
24 /** Does this card represent a nullable field. ? */
25 private boolean nullable;
26 /** A flag indicating whether or not this is a string value */
27 private boolean isString;
28 /** Maximum length of a FITS keyword field */
29 public static final int MAX_KEYWORD_LENGTH = 8;
30 /** Maximum length of a FITS value field */
31 public static final int MAX_VALUE_LENGTH = 70;
32 /** padding for building card images */
33 private static String space80 = " ";
35 /** Create a HeaderCard from its component parts
36 * @param key keyword (null for a comment)
37 * @param value value (null for a comment or keyword without an '=')
38 * @param comment comment
39 * @exception HeaderCardException for any invalid keyword
41 public HeaderCard(String key, double value, String comment)
42 throws HeaderCardException {
43 this(key, dblString(value), comment);
47 /** Create a HeaderCard from its component parts
48 * @param key keyword (null for a comment)
49 * @param value value (null for a comment or keyword without an '=')
50 * @param comment comment
51 * @exception HeaderCardException for any invalid keyword
53 public HeaderCard(String key, boolean value, String comment)
54 throws HeaderCardException {
55 this(key, value ? "T" : "F", comment);
59 /** Create a HeaderCard from its component parts
60 * @param key keyword (null for a comment)
61 * @param value value (null for a comment or keyword without an '=')
62 * @param comment comment
63 * @exception HeaderCardException for any invalid keyword
65 public HeaderCard(String key, int value, String comment)
66 throws HeaderCardException {
67 this(key, String.valueOf(value), comment);
71 /** Create a HeaderCard from its component parts
72 * @param key keyword (null for a comment)
73 * @param value value (null for a comment or keyword without an '=')
74 * @param comment comment
75 * @exception HeaderCardException for any invalid keyword
77 public HeaderCard(String key, long value, String comment)
78 throws HeaderCardException {
79 this(key, String.valueOf(value), comment);
83 /** Create a HeaderCard from its component parts
84 * @param key keyword (null for a comment)
85 * @param value value (null for a comment or keyword without an '=')
86 * @param comment comment
87 * @exception HeaderCardException for any invalid keyword or value
89 public HeaderCard(String key, String value, String comment)
90 throws HeaderCardException {
91 this(key, value, comment, false);
94 /** Create a comment style card.
95 * This constructor builds a card which has no value.
96 * This may be either a comment style card in which case the
97 * nullable field should be false, or a value field which
98 * has a null value, in which case the nullable field should be
100 * @param key The key for the comment or nullable field.
101 * @param comment The comment
102 * @param nullable Is this a nullable field or a comment-style card?
104 public HeaderCard(String key, String comment, boolean nullable)
105 throws HeaderCardException {
106 this(key, null, comment, nullable);
109 /** Create a string from a double making sure that it's
110 * not more than 20 characters long.
111 * Probably would be better if we had a way to override this
112 * since we can loose precision for some doubles.
114 private static String dblString(double input) {
115 String value = String.valueOf(input);
116 if (value.length() > 20) {
117 value = new java.util.Formatter().format("%20.13G", input).out().toString();
122 /** Create a HeaderCard from its component parts
123 * @param key Keyword (null for a COMMENT)
125 * @param comment Comment
126 * @param nullable Is this a nullable value card?
127 * @exception HeaderCardException for any invalid keyword or value
129 public HeaderCard(String key, String value, String comment, boolean nullable)
130 throws HeaderCardException {
131 if (comment != null && comment.startsWith("ntf::")) {
132 String ckey = comment.substring(5); // Get rid of ntf:: prefix
133 comment = HeaderCommentsMap.getComment(ckey);
135 if (key == null && value != null) {
136 throw new HeaderCardException("Null keyword with non-null value");
139 if (key != null && key.length() > MAX_KEYWORD_LENGTH) {
140 if (!FitsFactory.getUseHierarch()
141 || !key.substring(0, 9).equals("HIERARCH.")) {
142 throw new HeaderCardException("Keyword too long");
147 value = value.replaceAll(" *$", "");
149 if (value.length() > MAX_VALUE_LENGTH) {
150 throw new HeaderCardException("Value too long");
153 if (value.startsWith("'")) {
154 if (value.charAt(value.length() - 1) != '\'') {
155 throw new HeaderCardException("Missing end quote in string value");
158 value = value.substring(1, value.length() - 1).trim();
165 this.comment = comment;
166 this.nullable = nullable;
170 /** Create a HeaderCard from a FITS card image
171 * @param card the 80 character card image
173 public HeaderCard(String card) {
179 if (card.length() > 80) {
180 card = card.substring(0, 80);
183 if (FitsFactory.getUseHierarch()
185 && card.substring(0, 9).equals("HIERARCH ")) {
190 // We are going to assume that the value has no blanks in
191 // it unless it is enclosed in quotes. Also, we assume that
192 // a / terminates the string (except inside quotes)
194 // treat short lines as special keywords
195 if (card.length() < 9) {
201 key = card.substring(0, 8).trim();
203 // if it is an empty key, assume the remainder of the card is a comment
204 if (key.length() == 0) {
206 comment = card.substring(8);
210 // Non-key/value pair lines are treated as keyed comments
211 if (key.equals("COMMENT") || key.equals("HISTORY")
212 || !card.substring(8, 10).equals("= ")) {
213 comment = card.substring(8).trim();
217 // extract the value/comment part of the string
218 String valueAndComment = card.substring(10).trim();
220 // If there is no value/comment part, we are done.
221 if (valueAndComment.length() == 0) {
227 boolean quote = false;
229 // If we have a ' then find the matching '.
230 if (valueAndComment.charAt(0) == '\'') {
233 while (offset < valueAndComment.length()) {
235 // look for next single-quote character
236 vend = valueAndComment.indexOf("'", offset);
238 // if the quote character is the last character on the line...
239 if (vend == valueAndComment.length() - 1) {
243 // if we did not find a matching single-quote...
245 // pretend this is a comment card
251 // if this is not an escaped single-quote, we are done
252 if (valueAndComment.charAt(vend + 1) != '\'') {
256 // skip past escaped single-quote
260 // break apart character string
261 value = valueAndComment.substring(1, vend).trim();
262 value = value.replace("''", "'");
265 if (vend + 1 >= valueAndComment.length()) {
269 comment = valueAndComment.substring(vend + 1).trim();
270 if (comment.charAt(0) == '/') {
271 if (comment.length() > 1) {
272 comment = comment.substring(1);
278 if (comment.length() == 0) {
288 // look for a / to terminate the field.
289 int slashLoc = valueAndComment.indexOf('/');
290 if (slashLoc != -1) {
291 comment = valueAndComment.substring(slashLoc + 1).trim();
292 value = valueAndComment.substring(0, slashLoc).trim();
294 value = valueAndComment;
299 /** Process HIERARCH style cards...
300 * HIERARCH LEV1 LEV2 ... = value / comment
301 * The keyword for the card will be "HIERARCH.LEV1.LEV2..."
302 * A '/' is assumed to start a comment.
304 private void hierarchCard(String card) {
308 String separator = "";
313 // First get the hierarchy levels
314 while ((tokLimits = getToken(card, posit)) != null) {
315 token = card.substring(tokLimits[0], tokLimits[1]);
316 if (!token.equals("=")) {
317 name += separator + token;
320 tokLimits = getToken(card, tokLimits[1]);
321 if (tokLimits != null) {
322 token = card.substring(tokLimits[0], tokLimits[1]);
331 posit = tokLimits[1];
337 if (tokLimits == null) {
344 // Really should consolidate the two instances
345 // of this test in this class!
346 if (token.charAt(0) == '\'') {
347 // Find the next undoubled quote...
349 if (token.length() > 1 && token.charAt(1) == '\''
350 && (token.length() == 2 || token.charAt(2) != '\'')) {
352 commStart = tokLimits[0] + 2;
353 } else if (card.length() < tokLimits[0] + 2) {
360 for (i = tokLimits[0] + 1; i < card.length(); i += 1) {
361 if (card.charAt(i) == '\'') {
362 if (i == card.length() - 1) {
363 value = card.substring(tokLimits[0] + 1, i);
366 } else if (card.charAt(i + 1) == '\'') {
371 value = card.substring(tokLimits[0] + 1, i);
384 for (int i = commStart; i < card.length(); i += 1) {
385 if (card.charAt(i) == '/') {
386 comment = card.substring(i + 1).trim();
388 } else if (card.charAt(i) != ' ') {
395 int sl = token.indexOf('/');
398 comment = card.substring(tokLimits[0] + 1);
400 value = token.substring(0, sl);
401 comment = card.substring(tokLimits[0] + sl + 1);
405 for (int i = tokLimits[1]; i < card.length(); i += 1) {
406 if (card.charAt(i) == '/') {
407 comment = card.substring(i + 1).trim();
409 } else if (card.charAt(i) != ' ') {
418 /** Get the next token. Can't use StringTokenizer
419 * since we sometimes need to know the position within
422 private int[] getToken(String card, int posit) {
425 for (i = posit; i < card.length(); i += 1) {
426 if (card.charAt(i) != ' ') {
431 if (i >= card.length()) {
435 if (card.charAt(i) == '=') {
436 return new int[]{i, i + 1};
440 for (j = i + 1; j < card.length(); j += 1) {
441 if (card.charAt(j) == ' ' || card.charAt(j) == '=') {
445 return new int[]{i, j};
448 /** Does this card contain a string value?
450 public boolean isStringValue() {
454 /** Is this a key/value card?
456 public boolean isKeyValuePair() {
457 return (key != null && value != null);
462 void setKey(String newKey) {
466 /** Return the keyword from this card
468 public String getKey() {
472 /** Return the value from this card
474 public String getValue() {
478 /** Set the value for this card.
480 public void setValue(String update) {
484 /** Return the comment from this card
486 public String getComment() {
490 /** Return the 80 character card image
492 public String toString() {
493 StringBuffer buf = new StringBuffer(80);
495 // start with the keyword, if there is one
497 if (key.length() > 9 && key.substring(0, 9).equals("HIERARCH.")) {
498 return hierarchToString();
501 if (key.length() < 8) {
502 buf.append(space80.substring(0, 8 - buf.length()));
506 if (value != null || nullable) {
512 // left justify the string inside the quotes
514 buf.append(value.replace("'", "''"));
515 if (buf.length() < 19) {
517 buf.append(space80.substring(0, 19 - buf.length()));
520 // Now add space to the comment area starting at column 40
521 if (buf.length() < 30) {
522 buf.append(space80.substring(0, 30 - buf.length()));
527 int offset = buf.length();
528 if (value.length() < 20) {
529 buf.append(space80.substring(0, 20 - value.length()));
536 // Pad out a null value.
537 buf.append(space80.substring(0, 20));
540 // if there is a comment, add a comment delimiter
541 if (comment != null) {
545 } else if (comment != null && comment.startsWith("= ")) {
549 // finally, add any comment
550 if (comment != null) {
554 // make sure the final string is exactly 80 characters long
555 if (buf.length() > 80) {
560 if (buf.length() < 80) {
561 buf.append(space80.substring(0, 80 - buf.length()));
565 return buf.toString();
568 private String hierarchToString() {
571 StringBuffer b = new StringBuffer(80);
574 while (p < key.length()) {
575 int q = key.indexOf('.', p);
577 b.append(space + key.substring(p));
580 b.append(space + key.substring(p, q));
586 if (value != null || nullable) {
590 // Try to align values
591 int avail = 80 - (b.length() + value.length());
596 if (comment != null) {
597 avail -= 3 + comment.length();
600 if (avail > 0 && b.length() < 29) {
601 b.append(space80.substring(0, Math.min(avail, 29 - b.length())));
606 } else if (avail > 0 && value.length() < 10) {
607 b.append(space80.substring(0, Math.min(avail, 10 - value.length())));
613 } else if (b.length() < 30) {
615 // Pad out a null value
616 b.append(space80.substring(0, 30 - b.length()));
621 if (comment != null) {
622 b.append(" / " + comment);
624 if (b.length() < 80) {
625 b.append(space80.substring(0, 80 - b.length()));
627 String card = new String(b);
628 if (card.length() > 80) {
629 card = card.substring(0, 80);