1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 6269 6270 6271 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284 6285 6286 6287 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463
|
/* This file, group.c, contains the grouping convention suport routines. */
/* The FITSIO software was written by William Pence at the High Energy */
/* Astrophysic Science Archive Research Center (HEASARC) at the NASA */
/* Goddard Space Flight Center. */
/* */
/* The group.c module of CFITSIO was written by Donald G. Jennings of */
/* the INTEGRAL Science Data Centre (ISDC) under NASA contract task */
/* 66002J6. The above copyright laws apply. Copyright guidelines of The */
/* University of Geneva might also apply. */
/* The following routines are designed to create, read, and manipulate */
/* FITS Grouping Tables as defined in the FITS Grouping Convention paper */
/* by Jennings, Pence, Folk and Schlesinger. The development of the */
/* grouping structure was partially funded under the NASA AISRP Program. */
#include "fitsio2.h"
#include "group.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#if defined(WIN32) || defined(__WIN32__)
#include <direct.h> /* defines the getcwd function on Windows PCs */
#endif
#if defined(unix) || defined(__unix__) || defined(__unix)
#include <unistd.h> /* needed for getcwd prototype on unix machines */
#endif
#define HEX_ESCAPE '%'
/*---------------------------------------------------------------------------
Change record:
D. Jennings, 18/06/98, version 1.0 of group module delivered to B. Pence for
integration into CFITSIO 2.005
D. Jennings, 17/11/98, fixed bug in ffgtcpr(). Now use fits_find_nextkey()
correctly and insert auxiliary keyword records
directly before the TTYPE1 keyword in the copied
group table.
D. Jennings, 22/01/99, ffgmop() now looks for relative file paths when
the MEMBER_LOCATION information is given in a
grouping table.
D. Jennings, 01/02/99, ffgtop() now looks for relatve file paths when
the GRPLCn keyword value is supplied in the member
HDU header.
D. Jennings, 01/02/99, ffgtam() now trys to construct relative file paths
from the member's file to the group table's file
(and visa versa) when both the member's file and
group table file are of access type FILE://.
D. Jennings, 05/05/99, removed the ffgtcn() function; made obsolete by
fits_get_url().
D. Jennings, 05/05/99, updated entire module to handle partial URLs and
absolute URLs more robustly. Host dependent directory
paths are now converted to true URLs before being
read from/written to grouping tables.
D. Jennings, 05/05/99, added the following new functions (note, none of these
are directly callable by the application)
int fits_path2url()
int fits_url2path()
int fits_get_cwd()
int fits_get_url()
int fits_clean_url()
int fits_relurl2url()
int fits_encode_url()
int fits_unencode_url()
int fits_is_url_absolute()
-----------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
int ffgtcr(fitsfile *fptr, /* FITS file pointer */
char *grpname, /* name of the grouping table */
int grouptype, /* code specifying the type of
grouping table information:
GT_ID_ALL_URI 0 ==> defualt (all columns)
GT_ID_REF 1 ==> ID by reference
GT_ID_POS 2 ==> ID by position
GT_ID_ALL 3 ==> ID by ref. and position
GT_ID_REF_URI 11 ==> (1) + URI info
GT_ID_POS_URI 12 ==> (2) + URI info */
int *status )/* return status code */
/*
create a grouping table at the end of the current FITS file. This
function makes the last HDU in the file the CHDU, then calls the
fits_insert_group() function to actually create the new grouping table.
*/
{
int hdutype;
int hdunum;
if(*status != 0) return(*status);
*status = fits_get_num_hdus(fptr,&hdunum,status);
/* If hdunum is 0 then we are at the beginning of the file and
we actually haven't closed the first header yet, so don't do
anything more */
if (0 != hdunum) {
*status = fits_movabs_hdu(fptr,hdunum,&hdutype,status);
}
/* Now, the whole point of the above two fits_ calls was to get to
the end of file. Let's ignore errors at this point and keep
going since any error is likely to mean that we are already at the
EOF, or the file is fatally corrupted. If we are at the EOF then
the next fits_ call will be ok. If it's corrupted then the
next call will fail, but that's not big deal at this point.
*/
if (0 != *status ) *status = 0;
*status = fits_insert_group(fptr,grpname,grouptype,status);
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgtis(fitsfile *fptr, /* FITS file pointer */
char *grpname, /* name of the grouping table */
int grouptype, /* code specifying the type of
grouping table information:
GT_ID_ALL_URI 0 ==> defualt (all columns)
GT_ID_REF 1 ==> ID by reference
GT_ID_POS 2 ==> ID by position
GT_ID_ALL 3 ==> ID by ref. and position
GT_ID_REF_URI 11 ==> (1) + URI info
GT_ID_POS_URI 12 ==> (2) + URI info */
int *status) /* return status code */
/*
insert a grouping table just after the current HDU of the current FITS file.
This is the same as fits_create_group() only it allows the user to select
the place within the FITS file to add the grouping table.
*/
{
int tfields = 0;
int hdunum = 0;
int hdutype = 0;
int extver;
int i;
long pcount = 0;
char *ttype[6];
char *tform[6];
char ttypeBuff[102];
char tformBuff[54];
char extname[] = "GROUPING";
char keyword[FLEN_KEYWORD];
char keyvalue[FLEN_VALUE];
char comment[FLEN_COMMENT];
do
{
/* set up the ttype and tform character buffers */
for(i = 0; i < 6; ++i)
{
ttype[i] = ttypeBuff+(i*17);
tform[i] = tformBuff+(i*9);
}
/* define the columns required according to the grouptype parameter */
*status = ffgtdc(grouptype,0,0,0,0,0,0,ttype,tform,&tfields,status);
/* create the grouping table using the columns defined above */
*status = fits_insert_btbl(fptr,0,tfields,ttype,tform,NULL,
NULL,pcount,status);
if(*status != 0) continue;
/*
retrieve the hdu position of the new grouping table for
future use
*/
fits_get_hdu_num(fptr,&hdunum);
/*
add the EXTNAME and EXTVER keywords to the HDU just after the
TFIELDS keyword; for now the EXTVER value is set to 0, it will be
set to the correct value later on
*/
fits_read_keyword(fptr,"TFIELDS",keyvalue,comment,status);
fits_insert_key_str(fptr,"EXTNAME",extname,
"HDU contains a Grouping Table",status);
fits_insert_key_lng(fptr,"EXTVER",0,"Grouping Table vers. (this file)",
status);
/*
if the grpname parameter value was defined (Non NULL and non zero
length) then add the GRPNAME keyword and value
*/
if(grpname != NULL && strlen(grpname) > 0)
fits_insert_key_str(fptr,"GRPNAME",grpname,"Grouping Table name",
status);
/*
add the TNULL keywords and values for each integer column defined;
integer null values are zero (0) for the MEMBER_POSITION and
MEMBER_VERSION columns.
*/
for(i = 0; i < tfields && *status == 0; ++i)
{
if(strcasecmp(ttype[i],"MEMBER_POSITION") == 0 ||
strcasecmp(ttype[i],"MEMBER_VERSION") == 0)
{
sprintf(keyword,"TFORM%d",i+1);
*status = fits_read_key_str(fptr,keyword,keyvalue,comment,
status);
sprintf(keyword,"TNULL%d",i+1);
*status = fits_insert_key_lng(fptr,keyword,0,"Column Null Value",
status);
}
}
/*
determine the correct EXTVER value for the new grouping table
by finding the highest numbered grouping table EXTVER value
the currently exists
*/
for(extver = 1;
(fits_movnam_hdu(fptr,ANY_HDU,"GROUPING",extver,status)) == 0;
++extver);
if(*status == BAD_HDU_NUM) *status = 0;
/*
move back to the new grouping table HDU and update the EXTVER
keyword value
*/
fits_movabs_hdu(fptr,hdunum,&hdutype,status);
fits_modify_key_lng(fptr,"EXTVER",extver,"&",status);
}while(0);
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgtch(fitsfile *gfptr, /* FITS pointer to group */
int grouptype, /* code specifying the type of
grouping table information:
GT_ID_ALL_URI 0 ==> defualt (all columns)
GT_ID_REF 1 ==> ID by reference
GT_ID_POS 2 ==> ID by position
GT_ID_ALL 3 ==> ID by ref. and position
GT_ID_REF_URI 11 ==> (1) + URI info
GT_ID_POS_URI 12 ==> (2) + URI info */
int *status) /* return status code */
/*
Change the grouping table structure of the grouping table pointed to by
gfptr. The grouptype code specifies the new structure of the table. This
operation only adds or removes grouping table columns, it does not add
or delete group members (i.e., table rows). If the grouping table already
has the desired structure then no operations are performed and function
simply returns with a (0) success status code. If the requested structure
change creates new grouping table columns, then the column values for all
existing members will be filled with the appropriate null values.
*/
{
int xtensionCol, extnameCol, extverCol, positionCol, locationCol, uriCol;
int ncols = 0;
int colnum = 0;
int nrows = 0;
int grptype = 0;
int i,j;
long intNull = 0;
long tfields = 0;
char *tform[6];
char *ttype[6];
unsigned char charNull[1] = {'\0'};
char ttypeBuff[102];
char tformBuff[54];
char keyword[FLEN_KEYWORD];
char keyvalue[FLEN_VALUE];
char comment[FLEN_COMMENT];
if(*status != 0) return(*status);
do
{
/* set up the ttype and tform character buffers */
for(i = 0; i < 6; ++i)
{
ttype[i] = ttypeBuff+(i*17);
tform[i] = tformBuff+(i*9);
}
/* retrieve positions of all Grouping table reserved columns */
*status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol,
&locationCol,&uriCol,&grptype,status);
if(*status != 0) continue;
/* determine the total number of grouping table columns */
*status = fits_read_key_lng(gfptr,"TFIELDS",&tfields,comment,status);
/* define grouping table columns to be added to the configuration */
*status = ffgtdc(grouptype,xtensionCol,extnameCol,extverCol,positionCol,
locationCol,uriCol,ttype,tform,&ncols,status);
/*
delete any grouping tables columns that exist but do not belong to
new desired configuration; note that we delete before creating new
columns for (file size) efficiency reasons
*/
switch(grouptype)
{
case GT_ID_ALL_URI:
/* no columns to be deleted in this case */
break;
case GT_ID_REF:
if(positionCol != 0)
{
*status = fits_delete_col(gfptr,positionCol,status);
--tfields;
if(uriCol > positionCol) --uriCol;
if(locationCol > positionCol) --locationCol;
}
if(uriCol != 0)
{
*status = fits_delete_col(gfptr,uriCol,status);
--tfields;
if(locationCol > uriCol) --locationCol;
}
if(locationCol != 0)
*status = fits_delete_col(gfptr,locationCol,status);
break;
case GT_ID_POS:
if(xtensionCol != 0)
{
*status = fits_delete_col(gfptr,xtensionCol,status);
--tfields;
if(extnameCol > xtensionCol) --extnameCol;
if(extverCol > xtensionCol) --extverCol;
if(uriCol > xtensionCol) --uriCol;
if(locationCol > xtensionCol) --locationCol;
}
if(extnameCol != 0)
{
*status = fits_delete_col(gfptr,extnameCol,status);
--tfields;
if(extverCol > extnameCol) --extverCol;
if(uriCol > extnameCol) --uriCol;
if(locationCol > extnameCol) --locationCol;
}
if(extverCol != 0)
{
*status = fits_delete_col(gfptr,extverCol,status);
--tfields;
if(uriCol > extverCol) --uriCol;
if(locationCol > extverCol) --locationCol;
}
if(uriCol != 0)
{
*status = fits_delete_col(gfptr,uriCol,status);
--tfields;
if(locationCol > uriCol) --locationCol;
}
if(locationCol != 0)
{
*status = fits_delete_col(gfptr,locationCol,status);
--tfields;
}
break;
case GT_ID_ALL:
if(uriCol != 0)
{
*status = fits_delete_col(gfptr,uriCol,status);
--tfields;
if(locationCol > uriCol) --locationCol;
}
if(locationCol != 0)
{
*status = fits_delete_col(gfptr,locationCol,status);
--tfields;
}
break;
case GT_ID_REF_URI:
if(positionCol != 0)
{
*status = fits_delete_col(gfptr,positionCol,status);
--tfields;
}
break;
case GT_ID_POS_URI:
if(xtensionCol != 0)
{
*status = fits_delete_col(gfptr,xtensionCol,status);
--tfields;
if(extnameCol > xtensionCol) --extnameCol;
if(extverCol > xtensionCol) --extverCol;
}
if(extnameCol != 0)
{
*status = fits_delete_col(gfptr,extnameCol,status);
--tfields;
if(extverCol > extnameCol) --extverCol;
}
if(extverCol != 0)
{
*status = fits_delete_col(gfptr,extverCol,status);
--tfields;
}
break;
default:
*status = BAD_OPTION;
ffpmsg("Invalid value for grouptype parameter specified (ffgtch)");
break;
}
/*
add all the new grouping table columns that were not there
previously but are called for by the grouptype parameter
*/
for(i = 0; i < ncols && *status == 0; ++i)
*status = fits_insert_col(gfptr,tfields+i+1,ttype[i],tform[i],status);
/*
add the TNULL keywords and values for each new integer column defined;
integer null values are zero (0) for the MEMBER_POSITION and
MEMBER_VERSION columns. Insert a null ("/0") into each new string
column defined: MEMBER_XTENSION, MEMBER_NAME, MEMBER_URI_TYPE and
MEMBER_LOCATION. Note that by convention a null string is the
TNULL value for character fields so no TNULL is required.
*/
for(i = 0; i < ncols && *status == 0; ++i)
{
if(strcasecmp(ttype[i],"MEMBER_POSITION") == 0 ||
strcasecmp(ttype[i],"MEMBER_VERSION") == 0)
{
/* col contains int data; set TNULL and insert 0 for each col */
*status = fits_get_colnum(gfptr,CASESEN,ttype[i],&colnum,
status);
sprintf(keyword,"TFORM%d",colnum);
*status = fits_read_key_str(gfptr,keyword,keyvalue,comment,
status);
sprintf(keyword,"TNULL%d",colnum);
*status = fits_insert_key_lng(gfptr,keyword,0,
"Column Null Value",status);
for(j = 1; j <= nrows && *status == 0; ++j)
*status = fits_write_col_lng(gfptr,colnum,j,1,1,&intNull,
status);
}
else if(strcasecmp(ttype[i],"MEMBER_XTENSION") == 0 ||
strcasecmp(ttype[i],"MEMBER_NAME") == 0 ||
strcasecmp(ttype[i],"MEMBER_URI_TYPE") == 0 ||
strcasecmp(ttype[i],"MEMBER_LOCATION") == 0)
{
/* new col contains character data; insert NULLs into each col */
*status = fits_get_colnum(gfptr,CASESEN,ttype[i],&colnum,
status);
for(j = 1; j <= nrows && *status == 0; ++j)
/* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/
*status = fits_write_col_byt(gfptr,colnum,j,1,1,charNull,
status);
}
}
}while(0);
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgtrm(fitsfile *gfptr, /* FITS file pointer to group */
int rmopt, /* code specifying if member
elements are to be deleted:
OPT_RM_GPT ==> remove only group table
OPT_RM_ALL ==> recursively remove members
and their members (if groups) */
int *status) /* return status code */
/*
remove a grouping table, and optionally all its members. Any groups
containing the grouping table are updated, and all members (if not
deleted) have their GRPIDn and GRPLCn keywords updated accordingly.
If the (deleted) members are members of another grouping table then those
tables are also updated. The CHDU of the FITS file pointed to by gfptr must
be positioned to the grouping table to be deleted.
*/
{
int hdutype;
long i;
long nmembers = 0;
HDUtracker HDU;
if(*status != 0) return(*status);
/*
remove the grouping table depending upon the rmopt parameter
*/
switch(rmopt)
{
case OPT_RM_GPT:
/*
for this option, the grouping table is deleted, but the member
HDUs remain; in this case we only have to remove each member from
the grouping table by calling fits_remove_member() with the
OPT_RM_ENTRY option
*/
/* get the number of members contained by this table */
*status = fits_get_num_members(gfptr,&nmembers,status);
/* loop over all grouping table members and remove them */
for(i = nmembers; i > 0 && *status == 0; --i)
*status = fits_remove_member(gfptr,i,OPT_RM_ENTRY,status);
break;
case OPT_RM_ALL:
/*
for this option the entire Group is deleted -- this includes all
members and their members (if grouping tables themselves). Call
the recursive form of this function to perform the removal.
*/
/* add the current grouping table to the HDUtracker struct */
HDU.nHDU = 0;
*status = fftsad(gfptr,&HDU,NULL,NULL);
/* call the recursive group remove function */
*status = ffgtrmr(gfptr,&HDU,status);
/* free the memory allocated to the HDUtracker struct */
for(i = 0; i < HDU.nHDU; ++i)
{
free(HDU.filename[i]);
free(HDU.newFilename[i]);
}
break;
default:
*status = BAD_OPTION;
ffpmsg("Invalid value for the rmopt parameter specified (ffgtrm)");
break;
}
/*
if all went well then unlink and delete the grouping table HDU
*/
*status = ffgmul(gfptr,0,status);
*status = fits_delete_hdu(gfptr,&hdutype,status);
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgtcp(fitsfile *infptr, /* input FITS file pointer */
fitsfile *outfptr, /* output FITS file pointer */
int cpopt, /* code specifying copy options:
OPT_GCP_GPT (0) ==> copy only grouping table
OPT_GCP_ALL (2) ==> recusrively copy members
and their members (if
groups) */
int *status) /* return status code */
/*
copy a grouping table, and optionally all its members, to a new FITS file.
If the cpopt is set to OPT_GCP_GPT (copy grouping table only) then the
existing members have their GRPIDn and GRPLCn keywords updated to reflect
the existance of the new group, since they now belong to another group. If
cpopt is set to OPT_GCP_ALL (copy grouping table and members recursively)
then the original members are not updated; the new grouping table is
modified to include only the copied member HDUs and not the original members.
Note that the recursive version of this function, ffgtcpr(), is called
to perform the group table copy. In the case of cpopt == OPT_GCP_GPT
ffgtcpr() does not actually use recursion.
*/
{
int i;
HDUtracker HDU;
if(*status != 0) return(*status);
/* make sure infptr and outfptr are not the same pointer */
if(infptr == outfptr) *status = IDENTICAL_POINTERS;
else
{
/* initialize the HDUtracker struct */
HDU.nHDU = 0;
*status = fftsad(infptr,&HDU,NULL,NULL);
/*
call the recursive form of this function to copy the grouping table.
If the cpopt is OPT_GCP_GPT then there is actually no recursion
performed
*/
*status = ffgtcpr(infptr,outfptr,cpopt,&HDU,status);
/* free memory allocated for the HDUtracker struct */
for(i = 0; i < HDU.nHDU; ++i)
{
free(HDU.filename[i]);
free(HDU.newFilename[i]);
}
}
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgtmg(fitsfile *infptr, /* FITS file ptr to source grouping table */
fitsfile *outfptr, /* FITS file ptr to target grouping table */
int mgopt, /* code specifying merge options:
OPT_MRG_COPY (0) ==> copy members to target
group, leaving source
group in place
OPT_MRG_MOV (1) ==> move members to target
group, source group is
deleted after merge */
int *status) /* return status code */
/*
merge two grouping tables by combining their members into a single table.
The source grouping table must be the CHDU of the fitsfile pointed to by
infptr, and the target grouping table must be the CHDU of the fitsfile to by
outfptr. All members of the source grouping table shall be copied to the
target grouping table. If the mgopt parameter is OPT_MRG_COPY then the source
grouping table continues to exist after the merge. If the mgopt parameter
is OPT_MRG_MOV then the source grouping table is deleted after the merge,
and all member HDUs are updated accordingly.
*/
{
long i ;
long nmembers = 0;
fitsfile *tmpfptr = NULL;
if(*status != 0) return(*status);
do
{
*status = fits_get_num_members(infptr,&nmembers,status);
for(i = 1; i <= nmembers && *status == 0; ++i)
{
*status = fits_open_member(infptr,i,&tmpfptr,status);
*status = fits_add_group_member(outfptr,tmpfptr,0,status);
if(*status == HDU_ALREADY_MEMBER) *status = 0;
if(tmpfptr != NULL)
{
fits_close_file(tmpfptr,status);
tmpfptr = NULL;
}
}
if(*status != 0) continue;
if(mgopt == OPT_MRG_MOV)
*status = fits_remove_group(infptr,OPT_RM_GPT,status);
}while(0);
if(tmpfptr != NULL)
{
fits_close_file(tmpfptr,status);
}
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgtcm(fitsfile *gfptr, /* FITS file pointer to grouping table */
int cmopt, /* code specifying compact options
OPT_CMT_MBR (1) ==> compact only direct
members (if groups)
OPT_CMT_MBR_DEL (11) ==> (1) + delete all
compacted groups */
int *status) /* return status code */
/*
"Compact" a group pointed to by the FITS file pointer gfptr. This
is achieved by flattening the tree structure of a group and its
(grouping table) members. All members HDUs of a grouping table which is
itself a member of the grouping table gfptr are added to gfptr. Optionally,
the grouping tables which are "compacted" are deleted. If the grouping
table contains no members that are themselves grouping tables then this
function performs a NOOP.
*/
{
long i;
long nmembers = 0;
char keyvalue[FLEN_VALUE];
char comment[FLEN_COMMENT];
fitsfile *mfptr = NULL;
if(*status != 0) return(*status);
do
{
if(cmopt != OPT_CMT_MBR && cmopt != OPT_CMT_MBR_DEL)
{
*status = BAD_OPTION;
ffpmsg("Invalid value for cmopt parameter specified (ffgtcm)");
continue;
}
/* reteive the number of grouping table members */
*status = fits_get_num_members(gfptr,&nmembers,status);
/*
loop over all the grouping table members; if the member is a
grouping table then merge its members with the parent grouping
table
*/
for(i = 1; i <= nmembers && *status == 0; ++i)
{
*status = fits_open_member(gfptr,i,&mfptr,status);
if(*status != 0) continue;
*status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,comment,status);
/* if no EXTNAME keyword then cannot be a grouping table */
if(*status == KEY_NO_EXIST)
{
*status = 0;
continue;
}
prepare_keyvalue(keyvalue);
if(*status != 0) continue;
/* if EXTNAME == "GROUPING" then process member as grouping table */
if(strcasecmp(keyvalue,"GROUPING") == 0)
{
/* merge the member (grouping table) into the grouping table */
*status = fits_merge_groups(mfptr,gfptr,OPT_MRG_COPY,status);
*status = fits_close_file(mfptr,status);
mfptr = NULL;
/*
remove the member from the grouping table now that all of
its members have been transferred; if cmopt is set to
OPT_CMT_MBR_DEL then remove and delete the member
*/
if(cmopt == OPT_CMT_MBR)
*status = fits_remove_member(gfptr,i,OPT_RM_ENTRY,status);
else
*status = fits_remove_member(gfptr,i,OPT_RM_MBR,status);
}
else
{
/* not a grouping table; just close the opened member */
*status = fits_close_file(mfptr,status);
mfptr = NULL;
}
}
}while(0);
return(*status);
}
/*--------------------------------------------------------------------------*/
int ffgtvf(fitsfile *gfptr, /* FITS file pointer to group */
long *firstfailed, /* Member ID (if positive) of first failed
member HDU verify check or GRPID index
(if negitive) of first failed group
link verify check. */
int *status) /* return status code */
/*
check the integrity of a grouping table to make sure that all group members
are accessible and all the links to other grouping tables are valid. The
firstfailed parameter returns the member ID of the first member HDU to fail
verification if positive or the first group link to fail if negative;
otherwise firstfailed contains a return value of 0.
*/
{
long i;
long nmembers = 0;
long ngroups = 0;
char errstr[FLEN_VALUE];
fitsfile *fptr = NULL;
if(*status != 0) return(*status);
*firstfailed = 0;
do
{
/*
attempt to open all the members of the grouping table. We stop
at the first member which cannot be opened (which implies that it
cannot be located)
*/
*status = fits_get_num_members(gfptr,&nmembers,status);
for(i = 1; i <= nmembers && *status == 0; ++i)
{
*status = fits_open_member(gfptr,i,&fptr,status);
fits_close_file(fptr,status);
}
/*
if the status is non-zero from the above loop then record the
member index that caused the error
*/
if(*status != 0)
{
*firstfailed = i;
sprintf(errstr,"Group table verify failed for member %ld (ffgtvf)",
i);
ffpmsg(errstr);
continue;
}
/*
attempt to open all the groups linked to this grouping table. We stop
at the first group which cannot be opened (which implies that it
cannot be located)
*/
*status = fits_get_num_groups(gfptr,&ngroups,status);
for(i = 1; i <= ngroups && *status == 0; ++i)
{
*status = fits_open_group(gfptr,i,&fptr,status);
fits_close_file(fptr,status);
}
/*
if the status from the above loop is non-zero, then record the
GRPIDn index of the group that caused the failure
*/
if(*status != 0)
{
*firstfailed = -1*i;
sprintf(errstr,
"Group table verify failed for GRPID index %ld (ffgtvf)",i);
ffpmsg(errstr);
continue;
}
}while(0);
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgtop(fitsfile *mfptr, /* FITS file pointer to the member HDU */
int grpid, /* group ID (GRPIDn index) within member HDU */
fitsfile **gfptr, /* FITS file pointer to grouping table HDU */
int *status) /* return status code */
/*
open the grouping table that contains the member HDU. The member HDU must
be the CHDU of the FITS file pointed to by mfptr, and the grouping table
is identified by the Nth index number of the GRPIDn keywords specified in
the member HDU's header. The fitsfile gfptr pointer is positioned with the
appropriate FITS file with the grouping table as the CHDU. If the group
grouping table resides in a file other than the member then an attempt
is first made to open the file readwrite, and failing that readonly.
Note that it is possible for the GRPIDn/GRPLCn keywords in a member
header to be non-continuous, e.g., GRPID1, GRPID2, GRPID5, GRPID6. In
such cases, the grpid index value specified in the function call shall
identify the (grpid)th GRPID value. In the above example, if grpid == 3,
then the group specified by GRPID5 would be opened.
*/
{
int i;
int found;
long ngroups = 0;
long grpExtver = 0;
char keyword[FLEN_KEYWORD];
char keyvalue[FLEN_FILENAME];
char *tkeyvalue;
char location[FLEN_FILENAME];
char location1[FLEN_FILENAME];
char location2[FLEN_FILENAME];
char comment[FLEN_COMMENT];
char *url[2];
if(*status != 0) return(*status);
do
{
/* set the grouping table pointer to NULL for error checking later */
*gfptr = NULL;
/*
make sure that the group ID requested is valid ==> cannot be
larger than the number of GRPIDn keywords in the member HDU header
*/
*status = fits_get_num_groups(mfptr,&ngroups,status);
if(grpid > ngroups)
{
*status = BAD_GROUP_ID;
sprintf(comment,
"GRPID index %d larger total GRPID keywords %ld (ffgtop)",
grpid,ngroups);
ffpmsg(comment);
continue;
}
/*
find the (grpid)th group that the member HDU belongs to and read
the value of the GRPID(grpid) keyword; fits_get_num_groups()
automatically re-enumerates the GRPIDn/GRPLCn keywords to fill in
any gaps
*/
sprintf(keyword,"GRPID%d",grpid);
*status = fits_read_key_lng(mfptr,keyword,&grpExtver,comment,status);
if(*status != 0) continue;
/*
if the value of the GRPIDn keyword is positive then the member is
in the same FITS file as the grouping table and we only have to
reopen the current FITS file. Else the member and grouping table
HDUs reside in different files and another FITS file must be opened
as specified by the corresponding GRPLCn keyword
The DO WHILE loop only executes once and is used to control the
file opening logic.
*/
do
{
if(grpExtver > 0)
{
/*
the member resides in the same file as the grouping
table, so just reopen the grouping table file
*/
*status = fits_reopen_file(mfptr,gfptr,status);
continue;
}
else if(grpExtver == 0)
{
/* a GRPIDn value of zero (0) is undefined */
*status = BAD_GROUP_ID;
sprintf(comment,"Invalid value of %ld for GRPID%d (ffgtop)",
grpExtver,grpid);
ffpmsg(comment);
continue;
}
/*
The GRPLCn keyword value is negative, which implies that
the grouping table must reside in another FITS file;
search for the corresponding GRPLCn keyword
*/
/* set the grpExtver value positive */
grpExtver = -1*grpExtver;
/* read the GRPLCn keyword value */
sprintf(keyword,"GRPLC%d",grpid);
/* SPR 1738 */
*status = fits_read_key_longstr(mfptr,keyword,&tkeyvalue,comment,
status);
if (0 == *status) {
strcpy(keyvalue,tkeyvalue);
free(tkeyvalue);
}
/* if the GRPLCn keyword was not found then there is a problem */
if(*status == KEY_NO_EXIST)
{
*status = BAD_GROUP_ID;
sprintf(comment,"Cannot find GRPLC%d keyword (ffgtop)",
grpid);
ffpmsg(comment);
continue;
}
prepare_keyvalue(keyvalue);
/*
if the GRPLCn keyword value specifies an absolute URL then
try to open the file; we cannot attempt any relative URL
or host-dependent file path reconstruction
*/
if(fits_is_url_absolute(keyvalue))
{
ffpmsg("Try to open group table file as absolute URL (ffgtop)");
*status = fits_open_file(gfptr,keyvalue,READWRITE,status);
/* if the open was successful then continue */
if(*status == 0) continue;
/* if READWRITE failed then try opening it READONLY */
ffpmsg("OK, try open group table file as READONLY (ffgtop)");
*status = 0;
*status = fits_open_file(gfptr,keyvalue,READONLY,status);
/* continue regardless of the outcome */
continue;
}
/*
see if the URL gives a file path that is absolute on the
host machine
*/
*status = fits_url2path(keyvalue,location1,status);
*status = fits_open_file(gfptr,location1,READWRITE,status);
/* if the file opened then continue */
if(*status == 0) continue;
/* if READWRITE failed then try opening it READONLY */
ffpmsg("OK, try open group table file as READONLY (ffgtop)");
*status = 0;
*status = fits_open_file(gfptr,location1,READONLY,status);
/* if the file opened then continue */
if(*status == 0) continue;
/*
the grouping table location given by GRPLCn must specify a
relative URL. We assume that this URL is relative to the
member HDU's FITS file. Try to construct a full URL location
for the grouping table's FITS file and then open it
*/
*status = 0;
/* retrieve the URL information for the member HDU's file */
url[0] = location1; url[1] = location2;
*status = fits_get_url(mfptr,url[0],url[1],NULL,NULL,NULL,status);
/*
It is possible that the member HDU file has an initial
URL it was opened with and a real URL that the file actually
exists at (e.g., an HTTP accessed file copied to a local
file). For each possible URL try to construct a
*/
for(i = 0, found = 0, *gfptr = NULL; i < 2 && !found; ++i)
{
/* the url string could be empty */
if(*url[i] == 0) continue;
/*
create a full URL from the partial and the member
HDU file URL
*/
*status = fits_relurl2url(url[i],keyvalue,location,status);
/* if an error occured then contniue */
if(*status != 0)
{
*status = 0;
continue;
}
/*
if the location does not specify an access method
then turn it into a host dependent path
*/
if(! fits_is_url_absolute(location))
{
*status = fits_url2path(location,url[i],status);
strcpy(location,url[i]);
}
/* try to open the grouping table file READWRITE */
*status = fits_open_file(gfptr,location,READWRITE,status);
if(*status != 0)
{
/* try to open the grouping table file READONLY */
ffpmsg("opening file as READWRITE failed (ffgtop)");
ffpmsg("OK, try to open file as READONLY (ffgtop)");
*status = 0;
*status = fits_open_file(gfptr,location,READONLY,status);
}
/* either set the found flag or reset the status flag */
if(*status == 0)
found = 1;
else
*status = 0;
}
}while(0); /* end of file opening loop */
/* if an error occured with the file opening then exit */
if(*status != 0) continue;
if(*gfptr == NULL)
{
ffpmsg("Cannot open or find grouping table FITS file (ffgtop)");
*status = GROUP_NOT_FOUND;
continue;
}
/* search for the grouping table in its FITS file */
*status = fits_movnam_hdu(*gfptr,ANY_HDU,"GROUPING",(int)grpExtver,
status);
if(*status != 0) *status = GROUP_NOT_FOUND;
}while(0);
if(*status != 0 && *gfptr != NULL)
{
fits_close_file(*gfptr,status);
*gfptr = NULL;
}
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgtam(fitsfile *gfptr, /* FITS file pointer to grouping table HDU */
fitsfile *mfptr, /* FITS file pointer to member HDU */
int hdupos, /* member HDU position IF in the same file as
the grouping table AND mfptr == NULL */
int *status) /* return status code */
/*
add a member HDU to an existing grouping table. The fitsfile pointer gfptr
must be positioned with the grouping table as the CHDU. The member HDU
may either be identifed with the fitsfile *mfptr (which must be positioned
to the member HDU) or the hdupos parameter (the HDU number of the member
HDU) if both reside in the same FITS file. The hdupos value is only used
if the mfptr parameter has a value of NULL (0). The new member HDU shall
have the appropriate GRPIDn and GRPLCn keywords created in its header.
Note that if the member HDU to be added to the grouping table is already
a member of the group then it will not be added a sceond time.
*/
{
int xtensionCol,extnameCol,extverCol,positionCol,locationCol,uriCol;
int memberPosition = 0;
int grptype = 0;
int hdutype = 0;
int useLocation = 0;
int nkeys = 6;
int found;
int i;
int memberIOstate;
int groupIOstate;
int iomode;
long memberExtver = 0;
long groupExtver = 0;
long memberID = 0;
long nmembers = 0;
long ngroups = 0;
long grpid = 0;
char memberAccess1[FLEN_VALUE];
char memberAccess2[FLEN_VALUE];
char memberFileName[FLEN_FILENAME];
char memberLocation[FLEN_FILENAME];
char grplc[FLEN_FILENAME];
char *tgrplc;
char memberHDUtype[FLEN_VALUE];
char memberExtname[FLEN_VALUE];
char memberURI[] = "URL";
char groupAccess1[FLEN_VALUE];
char groupAccess2[FLEN_VALUE];
char groupFileName[FLEN_FILENAME];
char groupLocation[FLEN_FILENAME];
char tmprootname[FLEN_FILENAME], grootname[FLEN_FILENAME];
char cwd[FLEN_FILENAME];
char *keys[] = {"GRPNAME","EXTVER","EXTNAME","TFIELDS","GCOUNT","EXTEND"};
char *tmpPtr[1];
char keyword[FLEN_KEYWORD];
char card[FLEN_CARD];
unsigned char charNull[] = {'\0'};
fitsfile *tmpfptr = NULL;
int parentStatus = 0;
if(*status != 0) return(*status);
do
{
/*
make sure the grouping table can be modified before proceeding
*/
fits_file_mode(gfptr,&iomode,status);
if(iomode != READWRITE)
{
ffpmsg("cannot modify grouping table (ffgtam)");
*status = BAD_GROUP_ATTACH;
continue;
}
/*
if the calling function supplied the HDU position of the member
HDU instead of fitsfile pointer then get a fitsfile pointer
*/
if(mfptr == NULL)
{
*status = fits_reopen_file(gfptr,&tmpfptr,status);
*status = fits_movabs_hdu(tmpfptr,hdupos,&hdutype,status);
if(*status != 0) continue;
}
else
tmpfptr = mfptr;
/*
determine all the information about the member HDU that will
be needed later; note that we establish the default values for
all information values that are not explicitly found
*/
*status = fits_read_key_str(tmpfptr,"XTENSION",memberHDUtype,card,
status);
if(*status == KEY_NO_EXIST)
{
strcpy(memberHDUtype,"PRIMARY");
*status = 0;
}
prepare_keyvalue(memberHDUtype);
*status = fits_read_key_lng(tmpfptr,"EXTVER",&memberExtver,card,status);
if(*status == KEY_NO_EXIST)
{
memberExtver = 1;
*status = 0;
}
*status = fits_read_key_str(tmpfptr,"EXTNAME",memberExtname,card,
status);
if(*status == KEY_NO_EXIST)
{
memberExtname[0] = 0;
*status = 0;
}
prepare_keyvalue(memberExtname);
fits_get_hdu_num(tmpfptr,&memberPosition);
/*
Determine if the member HDU's FITS file location needs to be
taken into account when building its grouping table reference
If the member location needs to be used (==> grouping table and member
HDU reside in different files) then create an appropriate URL for
the member HDU's file and grouping table's file. Note that the logic
for this is rather complicated
*/
/* SPR 3463, don't do this
if(tmpfptr->Fptr == gfptr->Fptr)
{ */
/*
member HDU and grouping table reside in the same file, no need
to use the location information */
/* printf ("same file\n");
useLocation = 0;
memberIOstate = 1;
*memberFileName = 0;
}
else
{ */
/*
the member HDU and grouping table FITS file location information
must be used.
First determine the correct driver and file name for the group
table and member HDU files. If either are disk files then
construct an absolute file path for them. Finally, if both are
disk files construct relative file paths from the group(member)
file to the member(group) file.
*/
/* set the USELOCATION flag to true */
useLocation = 1;
/*
get the location, access type and iostate (RO, RW) of the
member HDU file
*/
*status = fits_get_url(tmpfptr,memberFileName,memberLocation,
memberAccess1,memberAccess2,&memberIOstate,
status);
/*
if the memberFileName string is empty then use the values of
the memberLocation string. This corresponds to a file where
the "real" file is a temporary memory file, and we must assume
the the application really wants the original file to be the
group member
*/
if(strlen(memberFileName) == 0)
{
strcpy(memberFileName,memberLocation);
strcpy(memberAccess1,memberAccess2);
}
/*
get the location, access type and iostate (RO, RW) of the
grouping table file
*/
*status = fits_get_url(gfptr,groupFileName,groupLocation,
groupAccess1,groupAccess2,&groupIOstate,
status);
if(*status != 0) continue;
/*
the grouping table file must be writable to continue
*/
if(groupIOstate == 0)
{
ffpmsg("cannot modify grouping table (ffgtam)");
*status = BAD_GROUP_ATTACH;
continue;
}
/*
determine how to construct the resulting URLs for the member and
group files
*/
if(strcasecmp(groupAccess1,"file://") &&
strcasecmp(memberAccess1,"file://"))
{
*cwd = 0;
/*
nothing to do in this case; both the member and group files
must be of an access type that already gives valid URLs;
i.e., URLs that we can pass directly to the file drivers
*/
}
else
{
/*
retrieve the Current Working Directory as a Unix-like
URL standard string
*/
*status = fits_get_cwd(cwd,status);
/*
create full file path for the member HDU FITS file URL
if it is of access type file://
*/
if(strcasecmp(memberAccess1,"file://") == 0)
{
if(*memberFileName == '/')
{
strcpy(memberLocation,memberFileName);
}
else
{
strcpy(memberLocation,cwd);
strcat(memberLocation,"/");
strcat(memberLocation,memberFileName);
}
*status = fits_clean_url(memberLocation,memberFileName,
status);
}
/*
create full file path for the grouping table HDU FITS file URL
if it is of access type file://
*/
if(strcasecmp(groupAccess1,"file://") == 0)
{
if(*groupFileName == '/')
{
strcpy(groupLocation,groupFileName);
}
else
{
strcpy(groupLocation,cwd);
strcat(groupLocation,"/");
strcat(groupLocation,groupFileName);
}
*status = fits_clean_url(groupLocation,groupFileName,status);
}
/*
if both the member and group files are disk files then
create a relative path (relative URL) strings with
respect to the grouping table's file and the grouping table's
file with respect to the member HDU's file
*/
if(strcasecmp(groupAccess1,"file://") == 0 &&
strcasecmp(memberAccess1,"file://") == 0)
{
fits_url2relurl(memberFileName,groupFileName,
groupLocation,status);
fits_url2relurl(groupFileName,memberFileName,
memberLocation,status);
/*
copy the resulting partial URL strings to the
memberFileName and groupFileName variables for latter
use in the function
*/
strcpy(memberFileName,memberLocation);
strcpy(groupFileName,groupLocation);
}
}
/* beo done */
/* } */
/* retrieve the grouping table's EXTVER value */
*status = fits_read_key_lng(gfptr,"EXTVER",&groupExtver,card,status);
/*
if useLocation is true then make the group EXTVER value negative
for the subsequent GRPIDn/GRPLCn matching
*/
/* SPR 3463 change test; WDP added test for same filename */
/* Now, if either the Fptr values are the same, or the root filenames
are the same, then assume these refer to the same file.
*/
fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status);
fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
if((tmpfptr->Fptr != gfptr->Fptr) &&
strncmp(tmprootname, grootname, FLEN_FILENAME))
groupExtver = -1*groupExtver;
/* retrieve the number of group members */
*status = fits_get_num_members(gfptr,&nmembers,status);
do {
/*
make sure the member HDU is not already an entry in the
grouping table before adding it
*/
*status = ffgmf(gfptr,memberHDUtype,memberExtname,memberExtver,
memberPosition,memberFileName,&memberID,status);
if(*status == MEMBER_NOT_FOUND) *status = 0;
else if(*status == 0)
{
parentStatus = HDU_ALREADY_MEMBER;
ffpmsg("Specified HDU is already a member of the Grouping table (ffgtam)");
continue;
}
else continue;
/*
if the member HDU is not already recorded in the grouping table
then add it
*/
/* add a new row to the grouping table */
*status = fits_insert_rows(gfptr,nmembers,1,status);
++nmembers;
/* retrieve the grouping table column IDs and structure type */
*status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol,
&locationCol,&uriCol,&grptype,status);
/* fill in the member HDU data in the new grouping table row */
*tmpPtr = memberHDUtype;
if(xtensionCol != 0)
fits_write_col_str(gfptr,xtensionCol,nmembers,1,1,tmpPtr,status);
*tmpPtr = memberExtname;
if(extnameCol != 0)
{
if(strlen(memberExtname) != 0)
fits_write_col_str(gfptr,extnameCol,nmembers,1,1,tmpPtr,status);
else
/* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/
fits_write_col_byt(gfptr,extnameCol,nmembers,1,1,charNull,status);
}
if(extverCol != 0)
fits_write_col_lng(gfptr,extverCol,nmembers,1,1,&memberExtver,
status);
if(positionCol != 0)
fits_write_col_int(gfptr,positionCol,nmembers,1,1,
&memberPosition,status);
*tmpPtr = memberFileName;
if(locationCol != 0)
{
/* Change the test for SPR 3463 */
/* Now, if either the Fptr values are the same, or the root filenames
are the same, then assume these refer to the same file.
*/
fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status);
fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
if((tmpfptr->Fptr != gfptr->Fptr) &&
strncmp(tmprootname, grootname, FLEN_FILENAME))
fits_write_col_str(gfptr,locationCol,nmembers,1,1,tmpPtr,status);
else
/* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/
fits_write_col_byt(gfptr,locationCol,nmembers,1,1,charNull,status);
}
*tmpPtr = memberURI;
if(uriCol != 0)
{
/* Change the test for SPR 3463 */
/* Now, if either the Fptr values are the same, or the root filenames
are the same, then assume these refer to the same file.
*/
fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status);
fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
if((tmpfptr->Fptr != gfptr->Fptr) &&
strncmp(tmprootname, grootname, FLEN_FILENAME))
fits_write_col_str(gfptr,uriCol,nmembers,1,1,tmpPtr,status);
else
/* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/
fits_write_col_byt(gfptr,uriCol,nmembers,1,1,charNull,status);
}
} while(0);
if(0 != *status) continue;
/*
add GRPIDn/GRPLCn keywords to the member HDU header to link
it to the grouing table if the they do not already exist and
the member file is RW
*/
fits_file_mode(tmpfptr,&iomode,status);
if(memberIOstate == 0 || iomode != READWRITE)
{
ffpmsg("cannot add GRPID/LC keywords to member HDU: (ffgtam)");
ffpmsg(memberFileName);
continue;
}
*status = fits_get_num_groups(tmpfptr,&ngroups,status);
/*
look for the GRPID/LC keywords in the member HDU; if the keywords
for the back-link to the grouping table already exist then no
need to add them again
*/
for(i = 1, found = 0; i <= ngroups && !found && *status == 0; ++i)
{
sprintf(keyword,"GRPID%d",(int)ngroups);
*status = fits_read_key_lng(tmpfptr,keyword,&grpid,card,status);
if(grpid == groupExtver)
{
if(grpid < 0)
{
/* have to make sure the GRPLCn keyword matches too */
sprintf(keyword,"GRPLC%d",(int)ngroups);
/* SPR 1738 */
*status = fits_read_key_longstr(mfptr,keyword,&tgrplc,card,
status);
if (0 == *status) {
strcpy(grplc,tgrplc);
free(tgrplc);
}
/*
always compare files using absolute paths
the presence of a non-empty cwd indicates
that the file names may require conversion
to absolute paths
*/
if(0 < strlen(cwd)) {
/* temp buffer for use in assembling abs. path(s) */
char tmp[FLEN_FILENAME];
/* make grplc absolute if necessary */
if(!fits_is_url_absolute(grplc)) {
fits_path2url(grplc,groupLocation,status);
if(groupLocation[0] != '/')
{
strcpy(tmp, cwd);
strcat(tmp,"/");
strcat(tmp,groupLocation);
fits_clean_url(tmp,grplc,status);
}
}
/* make groupFileName absolute if necessary */
if(!fits_is_url_absolute(groupFileName)) {
fits_path2url(groupFileName,groupLocation,status);
if(groupLocation[0] != '/')
{
strcpy(tmp, cwd);
strcat(tmp,"/");
strcat(tmp,groupLocation);
/*
note: use groupLocation (which is not used
below this block), to store the absolute
file name instead of using groupFileName.
The latter may be needed unaltered if the
GRPLC is written below
*/
fits_clean_url(tmp,groupLocation,status);
}
}
}
/*
see if the grplc value and the group file name match
*/
if(strcmp(grplc,groupLocation) == 0) found = 1;
}
else
{
/* the match is found with GRPIDn alone */
found = 1;
}
}
}
/*
if FOUND is true then no need to continue
*/
if(found)
{
ffpmsg("HDU already has GRPID/LC keywords for group table (ffgtam)");
continue;
}
/*
add the GRPID/LC keywords to the member header for this grouping
table
If NGROUPS == 0 then we must position the header pointer to the
record where we want to insert the GRPID/LC keywords (the pointer
is already correctly positioned if the above search loop activiated)
*/
if(ngroups == 0)
{
/*
no GRPIDn/GRPLCn keywords currently exist in header so try
to position the header pointer to a desirable position
*/
for(i = 0, *status = KEY_NO_EXIST;
i < nkeys && *status == KEY_NO_EXIST; ++i)
{
*status = 0;
*status = fits_read_card(tmpfptr,keys[i],card,status);
}
/* all else fails: move write pointer to end of header */
if(*status == KEY_NO_EXIST)
{
*status = 0;
fits_get_hdrspace(tmpfptr,&nkeys,&i,status);
ffgrec(tmpfptr,nkeys,card,status);
}
/* any other error status then abort */
if(*status != 0) continue;
}
/*
now that the header pointer is positioned for the GRPID/LC
keyword insertion increment the number of group links counter for
the member HDU
*/
++ngroups;
/*
if the member HDU and grouping table reside in the same FITS file
then there is no need to add a GRPLCn keyword
*/
/* SPR 3463 change test */
/* Now, if either the Fptr values are the same, or the root filenames
are the same, then assume these refer to the same file.
*/
fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status);
fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
if((tmpfptr->Fptr == gfptr->Fptr) ||
strncmp(tmprootname, grootname, FLEN_FILENAME) == 0)
{
/* add the GRPIDn keyword only */
sprintf(keyword,"GRPID%d",(int)ngroups);
fits_insert_key_lng(tmpfptr,keyword,groupExtver,
"EXTVER of Group containing this HDU",status);
}
else
{
/* add the GRPIDn and GRPLCn keywords */
sprintf(keyword,"GRPID%d",(int)ngroups);
fits_insert_key_lng(tmpfptr,keyword,groupExtver,
"EXTVER of Group containing this HDU",status);
sprintf(keyword,"GRPLC%d",(int)ngroups);
/* SPR 1738 */
fits_insert_key_longstr(tmpfptr,keyword,groupFileName,
"URL of file containing Group",status);
fits_write_key_longwarn(tmpfptr,status);
}
}while(0);
/* close the tmpfptr pointer if it was opened in this function */
if(mfptr == NULL)
{
*status = fits_close_file(tmpfptr,status);
}
*status = 0 == *status ? parentStatus : *status;
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgtnm(fitsfile *gfptr, /* FITS file pointer to grouping table */
long *nmembers, /* member count of the groping table */
int *status) /* return status code */
/*
return the number of member HDUs in a grouping table. The fitsfile pointer
gfptr must be positioned with the grouping table as the CHDU. The number
of grouping table member HDUs is just the NAXIS2 value of the grouping
table.
*/
{
char keyvalue[FLEN_VALUE];
char comment[FLEN_COMMENT];
if(*status != 0) return(*status);
*status = fits_read_keyword(gfptr,"EXTNAME",keyvalue,comment,status);
if(*status == KEY_NO_EXIST)
*status = NOT_GROUP_TABLE;
else
{
prepare_keyvalue(keyvalue);
if(strcasecmp(keyvalue,"GROUPING") != 0)
{
*status = NOT_GROUP_TABLE;
ffpmsg("Specified HDU is not a Grouping table (ffgtnm)");
}
*status = fits_read_key_lng(gfptr,"NAXIS2",nmembers,comment,status);
}
return(*status);
}
/*--------------------------------------------------------------------------*/
int ffgmng(fitsfile *mfptr, /* FITS file pointer to member HDU */
long *ngroups, /* total number of groups linked to HDU */
int *status) /* return status code */
/*
return the number of groups to which a HDU belongs, as defined by the number
of GRPIDn/GRPLCn keyword records that appear in the HDU header. The
fitsfile pointer mfptr must be positioned with the member HDU as the CHDU.
Each time this function is called, the indicies of the GRPIDn/GRPLCn
keywords are checked to make sure they are continuous (ie no gaps) and
are re-enumerated to eliminate gaps if gaps are found to be present.
*/
{
int offset;
int index;
int newIndex;
int i;
long grpid;
char *inclist[] = {"GRPID#"};
char keyword[FLEN_KEYWORD];
char newKeyword[FLEN_KEYWORD];
char card[FLEN_CARD];
char comment[FLEN_COMMENT];
char *tkeyvalue;
if(*status != 0) return(*status);
*ngroups = 0;
/* reset the member HDU keyword counter to the beginning */
*status = ffgrec(mfptr,0,card,status);
/*
search for the number of GRPIDn keywords in the member HDU header
and count them with the ngroups variable
*/
while(*status == 0)
{
/* read the next GRPIDn keyword in the series */
*status = fits_find_nextkey(mfptr,inclist,1,NULL,0,card,status);
if(*status != 0) continue;
++(*ngroups);
}
if(*status == KEY_NO_EXIST) *status = 0;
/*
read each GRPIDn/GRPLCn keyword and adjust their index values so that
there are no gaps in the index count
*/
for(index = 1, offset = 0, i = 1; i <= *ngroups && *status == 0; ++index)
{
sprintf(keyword,"GRPID%d",index);
/* try to read the next GRPIDn keyword in the series */
*status = fits_read_key_lng(mfptr,keyword,&grpid,card,status);
/* if not found then increment the offset counter and continue */
if(*status == KEY_NO_EXIST)
{
*status = 0;
++offset;
}
else
{
/*
increment the number_keys_found counter and see if the index
of the keyword needs to be updated
*/
++i;
if(offset > 0)
{
/* compute the new index for the GRPIDn/GRPLCn keywords */
newIndex = index - offset;
/* update the GRPIDn keyword index */
sprintf(newKeyword,"GRPID%d",newIndex);
fits_modify_name(mfptr,keyword,newKeyword,status);
/* If present, update the GRPLCn keyword index */
sprintf(keyword,"GRPLC%d",index);
sprintf(newKeyword,"GRPLC%d",newIndex);
/* SPR 1738 */
*status = fits_read_key_longstr(mfptr,keyword,&tkeyvalue,comment,
status);
if (0 == *status) {
fits_delete_key(mfptr,keyword,status);
fits_insert_key_longstr(mfptr,newKeyword,tkeyvalue,comment,status);
fits_write_key_longwarn(mfptr,status);
free(tkeyvalue);
}
if(*status == KEY_NO_EXIST) *status = 0;
}
}
}
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgmop(fitsfile *gfptr, /* FITS file pointer to grouping table */
long member, /* member ID (row num) within grouping table */
fitsfile **mfptr, /* FITS file pointer to member HDU */
int *status) /* return status code */
/*
open a grouping table member, returning a pointer to the member's FITS file
with the CHDU set to the member HDU. The grouping table must be the CHDU of
the FITS file pointed to by gfptr. The member to open is identified by its
row number within the grouping table (first row/member == 1).
If the member resides in a FITS file different from the grouping
table the member file is first opened readwrite and if this fails then
it is opened readonly. For access type of FILE:// the member file is
searched for assuming (1) an absolute path is given, (2) a path relative
to the CWD is given, and (3) a path relative to the grouping table file
but not relative to the CWD is given. If all of these fail then the
error FILE_NOT_FOUND is returned.
*/
{
int xtensionCol,extnameCol,extverCol,positionCol,locationCol,uriCol;
int grptype,hdutype;
int dummy;
long hdupos = 0;
long extver = 0;
char xtension[FLEN_VALUE];
char extname[FLEN_VALUE];
char uri[FLEN_VALUE];
char grpLocation1[FLEN_FILENAME];
char grpLocation2[FLEN_FILENAME];
char mbrLocation1[FLEN_FILENAME];
char mbrLocation2[FLEN_FILENAME];
char mbrLocation3[FLEN_FILENAME];
char cwd[FLEN_FILENAME];
char card[FLEN_CARD];
char nstr[] = {'\0'};
char *tmpPtr[1];
if(*status != 0) return(*status);
do
{
/*
retrieve the Grouping Convention reserved column positions within
the grouping table
*/
*status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol,
&locationCol,&uriCol,&grptype,status);
if(*status != 0) continue;
/*
extract the member information from grouping table
*/
tmpPtr[0] = xtension;
if(xtensionCol != 0)
{
*status = fits_read_col_str(gfptr,xtensionCol,member,1,1,nstr,
tmpPtr,&dummy,status);
/* convert the xtension string to a hdutype code */
if(strcasecmp(xtension,"PRIMARY") == 0) hdutype = IMAGE_HDU;
else if(strcasecmp(xtension,"IMAGE") == 0) hdutype = IMAGE_HDU;
else if(strcasecmp(xtension,"TABLE") == 0) hdutype = ASCII_TBL;
else if(strcasecmp(xtension,"BINTABLE") == 0) hdutype = BINARY_TBL;
else hdutype = ANY_HDU;
}
tmpPtr[0] = extname;
if(extnameCol != 0)
*status = fits_read_col_str(gfptr,extnameCol,member,1,1,nstr,
tmpPtr,&dummy,status);
if(extverCol != 0)
*status = fits_read_col_lng(gfptr,extverCol,member,1,1,0,
(long*)&extver,&dummy,status);
if(positionCol != 0)
*status = fits_read_col_lng(gfptr,positionCol,member,1,1,0,
(long*)&hdupos,&dummy,status);
tmpPtr[0] = mbrLocation1;
if(locationCol != 0)
*status = fits_read_col_str(gfptr,locationCol,member,1,1,nstr,
tmpPtr,&dummy,status);
tmpPtr[0] = uri;
if(uriCol != 0)
*status = fits_read_col_str(gfptr,uriCol,member,1,1,nstr,
tmpPtr,&dummy,status);
if(*status != 0) continue;
/*
decide what FITS file the member HDU resides in and open the file
using the fitsfile* pointer mfptr; note that this logic is rather
complicated and is based primiarly upon if a URL specifier is given
for the member file in the grouping table
*/
switch(grptype)
{
case GT_ID_POS:
case GT_ID_REF:
case GT_ID_ALL:
/*
no location information is given so we must assume that the
member HDU resides in the same FITS file as the grouping table;
if the grouping table was incorrectly constructed then this
assumption will be false, but there is nothing to be done about
it at this point
*/
*status = fits_reopen_file(gfptr,mfptr,status);
break;
case GT_ID_REF_URI:
case GT_ID_POS_URI:
case GT_ID_ALL_URI:
/*
The member location column exists. Determine if the member
resides in the same file as the grouping table or in a
separate file; open the member file in either case
*/
if(strlen(mbrLocation1) == 0)
{
/*
since no location information was given we must assume
that the member is in the same FITS file as the grouping
table
*/
*status = fits_reopen_file(gfptr,mfptr,status);
}
else
{
/*
make sure the location specifiation is "URL"; we cannot
decode any other URI types at this time
*/
if(strcasecmp(uri,"URL") != 0)
{
*status = FILE_NOT_OPENED;
sprintf(card,
"Cannot open member HDU file with URI type %s (ffgmop)",
uri);
ffpmsg(card);
continue;
}
/*
The location string for the member is not NULL, so it
does not necessially reside in the same FITS file as the
grouping table.
Three cases are attempted for opening the member's file
in the following order:
1. The URL given for the member's file is absolute (i.e.,
access method supplied); try to open the member
2. The URL given for the member's file is not absolute but
is an absolute file path; try to open the member as a file
after the file path is converted to a host-dependent form
3. The URL given for the member's file is not absolute
and is given as a relative path to the location of the
grouping table's file. Create an absolute URL using the
grouping table's file URL and try to open the member.
If all three cases fail then an error is returned. In each
case the file is first opened in read/write mode and failing
that readonly mode.
The following DO loop is only used as a mechanism to break
(continue) when the proper file opening method is found
*/
do
{
/*
CASE 1:
See if the member URL is absolute (i.e., includes a
access directive) and if so open the file
*/
if(fits_is_url_absolute(mbrLocation1))
{
/*
the URL must specify an access method, which
implies that its an absolute reference
regardless of the access method, pass the whole
URL to the open function for processing
*/
ffpmsg("member URL is absolute, try open R/W (ffgmop)");
*status = fits_open_file(mfptr,mbrLocation1,READWRITE,
status);
if(*status == 0) continue;
*status = 0;
/*
now try to open file using full URL specs in
readonly mode
*/
ffpmsg("OK, now try to open read-only (ffgmop)");
*status = fits_open_file(mfptr,mbrLocation1,READONLY,
status);
/* break from DO loop regardless of status */
continue;
}
/*
CASE 2:
If we got this far then the member URL location
has no access type ==> FILE:// Try to open the member
file using the URL as is, i.e., assume that it is given
as absolute, if it starts with a '/' character
*/
ffpmsg("Member URL is of type FILE (ffgmop)");
if(*mbrLocation1 == '/')
{
ffpmsg("Member URL specifies abs file path (ffgmop)");
/*
convert the URL path to a host dependent path
*/
*status = fits_url2path(mbrLocation1,mbrLocation2,
status);
ffpmsg("Try to open member URL in R/W mode (ffgmop)");
*status = fits_open_file(mfptr,mbrLocation2,READWRITE,
status);
if(*status == 0) continue;
*status = 0;
/*
now try to open file using the URL as an absolute
path in readonly mode
*/
ffpmsg("OK, now try to open read-only (ffgmop)");
*status = fits_open_file(mfptr,mbrLocation2,READONLY,
status);
/* break from the Do loop regardless of the status */
continue;
}
/*
CASE 3:
If we got this far then the URL does not specify an
absoulte file path or URL with access method. Since
the path to the group table's file is (obviously) valid
for the CWD, create a full location string for the
member HDU using the grouping table URL as a basis
The only problem is that the grouping table file might
have two URLs, the original one used to open it and
the one that points to the real file being accessed
(i.e., a file accessed via HTTP but transferred to a
local disk file). Have to attempt to build a URL to
the member HDU file using both of these URLs if
defined.
*/
ffpmsg("Try to open member file as relative URL (ffgmop)");
/* get the URL information for the grouping table file */
*status = fits_get_url(gfptr,grpLocation1,grpLocation2,
NULL,NULL,NULL,status);
/*
if the "real" grouping table file URL is defined then
build a full url for the member HDU file using it
and try to open the member HDU file
*/
if(*grpLocation1)
{
/* make sure the group location is absolute */
if(! fits_is_url_absolute(grpLocation1) &&
*grpLocation1 != '/')
{
fits_get_cwd(cwd,status);
strcat(cwd,"/");
strcat(cwd,grpLocation1);
strcpy(grpLocation1,cwd);
}
/* create a full URL for the member HDU file */
*status = fits_relurl2url(grpLocation1,mbrLocation1,
mbrLocation2,status);
if(*status != 0) continue;
/*
if the URL does not have an access method given then
translate it into a host dependent file path
*/
if(! fits_is_url_absolute(mbrLocation2))
{
*status = fits_url2path(mbrLocation2,mbrLocation3,
status);
strcpy(mbrLocation2,mbrLocation3);
}
/* try to open the member file READWRITE */
*status = fits_open_file(mfptr,mbrLocation2,READWRITE,
status);
if(*status == 0) continue;
*status = 0;
/* now try to open in readonly mode */
ffpmsg("now try to open file as READONLY (ffgmop)");
*status = fits_open_file(mfptr,mbrLocation2,READONLY,
status);
if(*status == 0) continue;
*status = 0;
}
/*
if we got this far then either the "real" grouping table
file URL was not defined or all attempts to open the
resulting member HDU file URL failed.
if the "original" grouping table file URL is defined then
build a full url for the member HDU file using it
and try to open the member HDU file
*/
if(*grpLocation2)
{
/* make sure the group location is absolute */
if(! fits_is_url_absolute(grpLocation2) &&
*grpLocation2 != '/')
{
fits_get_cwd(cwd,status);
strcat(cwd,"/");
strcat(cwd,grpLocation2);
strcpy(grpLocation2,cwd);
}
/* create an absolute URL for the member HDU file */
*status = fits_relurl2url(grpLocation2,mbrLocation1,
mbrLocation2,status);
if(*status != 0) continue;
/*
if the URL does not have an access method given then
translate it into a host dependent file path
*/
if(! fits_is_url_absolute(mbrLocation2))
{
*status = fits_url2path(mbrLocation2,mbrLocation3,
status);
strcpy(mbrLocation2,mbrLocation3);
}
/* try to open the member file READWRITE */
*status = fits_open_file(mfptr,mbrLocation2,READWRITE,
status);
if(*status == 0) continue;
*status = 0;
/* now try to open in readonly mode */
ffpmsg("now try to open file as READONLY (ffgmop)");
*status = fits_open_file(mfptr,mbrLocation2,READONLY,
status);
if(*status == 0) continue;
*status = 0;
}
/*
if we got this far then the member HDU file could not
be opened using any method. Log the error.
*/
ffpmsg("Cannot open member HDU FITS file (ffgmop)");
*status = MEMBER_NOT_FOUND;
}while(0);
}
break;
default:
/* no default action */
break;
}
if(*status != 0) continue;
/*
attempt to locate the member HDU within its FITS file as determined
and opened above
*/
switch(grptype)
{
case GT_ID_POS:
case GT_ID_POS_URI:
/*
try to find the member hdu in the the FITS file pointed to
by mfptr based upon its HDU posistion value. Note that is
impossible to verify if the HDU is actually the correct HDU due
to a lack of information.
*/
*status = fits_movabs_hdu(*mfptr,(int)hdupos,&hdutype,status);
break;
case GT_ID_REF:
case GT_ID_REF_URI:
/*
try to find the member hdu in the FITS file pointed to
by mfptr based upon its XTENSION, EXTNAME and EXTVER keyword
values
*/
*status = fits_movnam_hdu(*mfptr,hdutype,extname,extver,status);
if(*status == BAD_HDU_NUM)
{
*status = MEMBER_NOT_FOUND;
ffpmsg("Cannot find specified member HDU (ffgmop)");
}
/*
if the above function returned without error then the
mfptr is pointed to the member HDU
*/
break;
case GT_ID_ALL:
case GT_ID_ALL_URI:
/*
if the member entry has reference information then use it
(ID by reference is safer than ID by position) else use
the position information
*/
if(strlen(xtension) > 0 && strlen(extname) > 0 && extver > 0)
{
/* valid reference info exists so use it */
/* try to find the member hdu in the grouping table's file */
*status = fits_movnam_hdu(*mfptr,hdutype,extname,extver,status);
if(*status == BAD_HDU_NUM)
{
*status = MEMBER_NOT_FOUND;
ffpmsg("Cannot find specified member HDU (ffgmop)");
}
}
else
{
*status = fits_movabs_hdu(*mfptr,(int)hdupos,&hdutype,
status);
if(*status == END_OF_FILE) *status = MEMBER_NOT_FOUND;
}
/*
if the above function returned without error then the
mfptr is pointed to the member HDU
*/
break;
default:
/* no default action */
break;
}
}while(0);
if(*status != 0 && *mfptr != NULL)
{
fits_close_file(*mfptr,status);
}
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgmcp(fitsfile *gfptr, /* FITS file pointer to group */
fitsfile *mfptr, /* FITS file pointer to new member
FITS file */
long member, /* member ID (row num) within grouping table */
int cpopt, /* code specifying copy options:
OPT_MCP_ADD (0) ==> add copied member to the
grouping table
OPT_MCP_NADD (1) ==> do not add member copy to
the grouping table
OPT_MCP_REPL (2) ==> replace current member
entry with member copy */
int *status) /* return status code */
/*
copy a member HDU of a grouping table to a new FITS file. The grouping table
must be the CHDU of the FITS file pointed to by gfptr. The copy of the
group member shall be appended to the end of the FITS file pointed to by
mfptr. If the cpopt parameter is set to OPT_MCP_ADD then the copy of the
member is added to the grouping table as a new member, if OPT_MCP_NADD
then the copied member is not added to the grouping table, and if
OPT_MCP_REPL then the copied member is used to replace the original member.
The copied member HDU also has its EXTVER value updated so that its
combination of XTENSION, EXTNAME and EXVTER is unique within its new
FITS file.
*/
{
int numkeys = 0;
int keypos = 0;
int hdunum = 0;
int hdutype = 0;
int i;
char *incList[] = {"GRPID#","GRPLC#"};
char extname[FLEN_VALUE];
char card[FLEN_CARD];
char comment[FLEN_COMMENT];
char keyname[FLEN_CARD];
char value[FLEN_CARD];
fitsfile *tmpfptr = NULL;
if(*status != 0) return(*status);
do
{
/* open the member HDU to be copied */
*status = fits_open_member(gfptr,member,&tmpfptr,status);
if(*status != 0) continue;
/*
if the member is a grouping table then copy it with a call to
fits_copy_group() using the "copy only the grouping table" option
if it is not a grouping table then copy the hdu with fits_copy_hdu()
remove all GRPIDn and GRPLCn keywords, and update the EXTVER keyword
value
*/
/* get the member HDU's EXTNAME value */
*status = fits_read_key_str(tmpfptr,"EXTNAME",extname,comment,status);
/* if no EXTNAME value was found then set the extname to a null string */
if(*status == KEY_NO_EXIST)
{
extname[0] = 0;
*status = 0;
}
else if(*status != 0) continue;
prepare_keyvalue(extname);
/* if a grouping table then copy with fits_copy_group() */
if(strcasecmp(extname,"GROUPING") == 0)
*status = fits_copy_group(tmpfptr,mfptr,OPT_GCP_GPT,status);
else
{
/* copy the non-grouping table HDU the conventional way */
*status = fits_copy_hdu(tmpfptr,mfptr,0,status);
ffgrec(mfptr,0,card,status);
/* delete all the GRPIDn and GRPLCn keywords in the copied HDU */
while(*status == 0)
{
*status = fits_find_nextkey(mfptr,incList,2,NULL,0,card,status);
*status = fits_get_hdrpos(mfptr,&numkeys,&keypos,status);
/* SPR 1738 */
*status = fits_read_keyn(mfptr,keypos-1,keyname,value,
comment,status);
*status = fits_read_record(mfptr,keypos-1,card,status);
*status = fits_delete_key(mfptr,keyname,status);
}
if(*status == KEY_NO_EXIST) *status = 0;
if(*status != 0) continue;
}
/*
if the member HDU does not have an EXTNAME keyword then add one
with a default value
*/
if(strlen(extname) == 0)
{
if(fits_get_hdu_num(tmpfptr,&hdunum) == 1)
{
strcpy(extname,"PRIMARY");
*status = fits_write_key_str(mfptr,"EXTNAME",extname,
"HDU was Formerly a Primary Array",
status);
}
else
{
strcpy(extname,"DEFAULT");
*status = fits_write_key_str(mfptr,"EXTNAME",extname,
"default EXTNAME set by CFITSIO",
status);
}
}
/*
update the member HDU's EXTVER value (add it if not present)
*/
fits_get_hdu_num(mfptr,&hdunum);
fits_get_hdu_type(mfptr,&hdutype,status);
/* set the EXTVER value to 0 for now */
*status = fits_modify_key_lng(mfptr,"EXTVER",0,NULL,status);
/* if the EXTVER keyword was not found then add it */
if(*status == KEY_NO_EXIST)
{
*status = 0;
*status = fits_read_key_str(mfptr,"EXTNAME",extname,comment,
status);
*status = fits_insert_key_lng(mfptr,"EXTVER",0,
"Extension version ID",status);
}
if(*status != 0) continue;
/* find the first available EXTVER value for the copied HDU */
for(i = 1; fits_movnam_hdu(mfptr,hdutype,extname,i,status) == 0; ++i);
*status = 0;
fits_movabs_hdu(mfptr,hdunum,&hdutype,status);
/* reset the copied member HDUs EXTVER value */
*status = fits_modify_key_lng(mfptr,"EXTVER",(long)i,NULL,status);
/*
perform member copy operations that are dependent upon the cpopt
parameter value
*/
switch(cpopt)
{
case OPT_MCP_ADD:
/*
add the copied member to the grouping table, leaving the
entry for the original member in place
*/
*status = fits_add_group_member(gfptr,mfptr,0,status);
break;
case OPT_MCP_NADD:
/*
nothing to do for this copy option
*/
break;
case OPT_MCP_REPL:
/*
remove the original member from the grouping table and add the
copied member in its place
*/
*status = fits_remove_member(gfptr,member,OPT_RM_ENTRY,status);
*status = fits_add_group_member(gfptr,mfptr,0,status);
break;
default:
*status = BAD_OPTION;
ffpmsg("Invalid value specified for the cmopt parameter (ffgmcp)");
break;
}
}while(0);
if(tmpfptr != NULL)
{
fits_close_file(tmpfptr,status);
}
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgmtf(fitsfile *infptr, /* FITS file pointer to source grouping table */
fitsfile *outfptr, /* FITS file pointer to target grouping table */
long member, /* member ID within source grouping table */
int tfopt, /* code specifying transfer opts:
OPT_MCP_ADD (0) ==> copy member to dest.
OPT_MCP_MOV (3) ==> move member to dest. */
int *status) /* return status code */
/*
transfer a group member from one grouping table to another. The source
grouping table must be the CHDU of the fitsfile pointed to by infptr, and
the destination grouping table must be the CHDU of the fitsfile to by
outfptr. If the tfopt parameter is OPT_MCP_ADD then the member is made a
member of the target group and remains a member of the source group. If
the tfopt parameter is OPT_MCP_MOV then the member is deleted from the
source group after the transfer to the destination group. The member to be
transfered is identified by its row number within the source grouping table.
*/
{
fitsfile *mfptr = NULL;
if(*status != 0) return(*status);
if(tfopt != OPT_MCP_MOV && tfopt != OPT_MCP_ADD)
{
*status = BAD_OPTION;
ffpmsg("Invalid value specified for the tfopt parameter (ffgmtf)");
}
else
{
/* open the member of infptr to be transfered */
*status = fits_open_member(infptr,member,&mfptr,status);
/* add the member to the outfptr grouping table */
*status = fits_add_group_member(outfptr,mfptr,0,status);
/* close the member HDU */
*status = fits_close_file(mfptr,status);
/*
if the tfopt is "move member" then remove it from the infptr
grouping table
*/
if(tfopt == OPT_MCP_MOV)
*status = fits_remove_member(infptr,member,OPT_RM_ENTRY,status);
}
return(*status);
}
/*---------------------------------------------------------------------------*/
int ffgmrm(fitsfile *gfptr, /* FITS file pointer to group table */
long member, /* member ID (row num) in the group */
int rmopt, /* code specifying the delete option:
OPT_RM_ENTRY ==> delete the member entry
OPT_RM_MBR ==> delete entry and member HDU */
int *status) /* return status code */
/*
remove a member HDU from a grouping table. The fitsfile pointer gfptr must
be positioned with the grouping table as the CHDU, and the member to
delete is identified by its row number in the table (first member == 1).
The rmopt parameter determines if the member entry is deleted from the
grouping table (in which case GRPIDn and GRPLCn keywords in the member
HDU's header shall be updated accordingly) or if the member HDU shall
itself be removed from its FITS file.
*/
{
int found;
int hdutype = 0;
int index;
int iomode = 0;
long i;
long ngroups = 0;
long nmembers = 0;
long groupExtver = 0;
long grpid = 0;
char grpLocation1[FLEN_FILENAME];
char grpLocation2[FLEN_FILENAME];
char grpLocation3[FLEN_FILENAME];
char cwd[FLEN_FILENAME];
char keyword[FLEN_KEYWORD];
/* SPR 1738 This can now be longer */
char grplc[FLEN_FILENAME];
char *tgrplc;
char keyvalue[FLEN_VALUE];
char card[FLEN_CARD];
char *editLocation;
char mrootname[FLEN_FILENAME], grootname[FLEN_FILENAME];
fitsfile *mfptr = NULL;
if(*status != 0) return(*status);
do
{
/*
make sure the grouping table can be modified before proceeding
*/
fits_file_mode(gfptr,&iomode,status);
if(iomode != READWRITE)
{
ffpmsg("cannot modify grouping table (ffgtam)");
*status = BAD_GROUP_DETACH;
continue;
}
/* open the group member to be deleted and get its IOstatus*/
*status = fits_open_member(gfptr,member,&mfptr,status);
*status = fits_file_mode(mfptr,&iomode,status);
/*
if the member HDU is to be deleted then call fits_unlink_member()
to remove it from all groups to which it belongs (including
this one) and then delete it. Note that if the member is a
grouping table then we have to recursively call fits_remove_member()
for each member of the member before we delete the member itself.
*/
if(rmopt == OPT_RM_MBR)
{
/* cannot delete a PHDU */
if(fits_get_hdu_num(mfptr,&hdutype) == 1)
{
*status = BAD_HDU_NUM;
continue;
}
/* determine if the member HDU is itself a grouping table */
*status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,card,status);
/* if no EXTNAME is found then the HDU cannot be a grouping table */
if(*status == KEY_NO_EXIST)
{
keyvalue[0] = 0;
*status = 0;
}
prepare_keyvalue(keyvalue);
/* Any other error is a reason to abort */
if(*status != 0) continue;
/* if the EXTNAME == GROUPING then the member is a grouping table */
if(strcasecmp(keyvalue,"GROUPING") == 0)
{
/* remove each of the grouping table members */
*status = fits_get_num_members(mfptr,&nmembers,status);
for(i = nmembers; i > 0 && *status == 0; --i)
*status = fits_remove_member(mfptr,i,OPT_RM_ENTRY,status);
if(*status != 0) continue;
}
/* unlink the member HDU from all groups that contain it */
*status = ffgmul(mfptr,0,status);
if(*status != 0) continue;
/* reset the grouping table HDU struct */
fits_set_hdustruc(gfptr,status);
/* delete the member HDU */
if(iomode != READONLY)
*status = fits_delete_hdu(mfptr,&hdutype,status);
}
else if(rmopt == OPT_RM_ENTRY)
{
/*
The member HDU is only to be removed as an entry from this
grouping table. Actions are (1) find the GRPIDn/GRPLCn
keywords that link the member to the grouping table, (2)
remove the GRPIDn/GRPLCn keyword from the member HDU header
and (3) remove the member entry from the grouping table
*/
/*
there is no need to seach for and remove the GRPIDn/GRPLCn
keywords from the member HDU if it has not been opened
in READWRITE mode
*/
if(iomode == READWRITE)
{
/*
determine the group EXTVER value of the grouping table; if
the member HDU and grouping table HDU do not reside in the
same file then set the groupExtver value to its negative
*/
*status = fits_read_key_lng(gfptr,"EXTVER",&groupExtver,card,
status);
/* Now, if either the Fptr values are the same, or the root filenames
are the same, then assume these refer to the same file.
*/
fits_parse_rootname(mfptr->Fptr->filename, mrootname, status);
fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
if((mfptr->Fptr != gfptr->Fptr) &&
strncmp(mrootname, grootname, FLEN_FILENAME))
groupExtver = -1*groupExtver;
/*
retrieve the URLs for the grouping table; note that it is
possible that the grouping table file has two URLs, the
one used to open it and the "real" one pointing to the
actual file being accessed
*/
*status = fits_get_url(gfptr,grpLocation1,grpLocation2,NULL,
NULL,NULL,status);
if(*status != 0) continue;
/*
if either of the group location strings specify a relative
file path then convert them into absolute file paths
*/
*status = fits_get_cwd(cwd,status);
if(*grpLocation1 != 0 && *grpLocation1 != '/' &&
!fits_is_url_absolute(grpLocation1))
{
strcpy(grpLocation3,cwd);
strcat(grpLocation3,"/");
strcat(grpLocation3,grpLocation1);
fits_clean_url(grpLocation3,grpLocation1,status);
}
if(*grpLocation2 != 0 && *grpLocation2 != '/' &&
!fits_is_url_absolute(grpLocation2))
{
strcpy(grpLocation3,cwd);
strcat(grpLocation3,"/");
strcat(grpLocation3,grpLocation2);
fits_clean_url(grpLocation3,grpLocation2,status);
}
/*
determine the number of groups to which the member HDU
belongs
*/
*status = fits_get_num_groups(mfptr,&ngroups,status);
/* reset the HDU keyword position counter to the beginning */
*status = ffgrec(mfptr,0,card,status);
/*
loop over all the GRPIDn keywords in the member HDU header
and find the appropriate GRPIDn and GRPLCn keywords that
identify it as belonging to the group
*/
for(index = 1, found = 0; index <= ngroups && *status == 0 &&
!found; ++index)
{
/* read the next GRPIDn keyword in the series */
sprintf(keyword,"GRPID%d",index);
*status = fits_read_key_lng(mfptr,keyword,&grpid,card,
status);
if(*status != 0) continue;
/*
grpid value == group EXTVER value then we could have a
match
*/
if(grpid == groupExtver && grpid > 0)
{
/*
if GRPID is positive then its a match because
both the member HDU and grouping table HDU reside
in the same FITS file
*/
found = index;
}
else if(grpid == groupExtver && grpid < 0)
{
/*
have to look at the GRPLCn value to determine a
match because the member HDU and grouping table
HDU reside in different FITS files
*/
sprintf(keyword,"GRPLC%d",index);
/* SPR 1738 */
*status = fits_read_key_longstr(mfptr,keyword,&tgrplc,
card, status);
if (0 == *status) {
strcpy(grplc,tgrplc);
free(tgrplc);
}
if(*status == KEY_NO_EXIST)
{
/*
no GRPLCn keyword value found ==> grouping
convention not followed; nothing we can do
about it, so just continue
*/
sprintf(card,"No GRPLC%d found for GRPID%d",
index,index);
ffpmsg(card);
*status = 0;
continue;
}
else if (*status != 0) continue;
/* construct the URL for the GRPLCn value */
prepare_keyvalue(grplc);
/*
if the grplc value specifies a relative path then
turn it into a absolute file path for comparison
purposes
*/
if(*grplc != 0 && !fits_is_url_absolute(grplc) &&
*grplc != '/')
{
/* No, wrong,
strcpy(grpLocation3,cwd);
should be */
*status = fits_file_name(mfptr,grpLocation3,status);
/* Remove everything after the last / */
if (NULL != (editLocation = strrchr(grpLocation3,'/'))) {
*editLocation = '\0';
}
strcat(grpLocation3,"/");
strcat(grpLocation3,grplc);
*status = fits_clean_url(grpLocation3,grplc,
status);
}
/*
if the absolute value of GRPIDn is equal to the
EXTVER value of the grouping table and (one of the
possible two) grouping table file URL matches the
GRPLCn keyword value then we hava a match
*/
if(strcmp(grplc,grpLocation1) == 0 ||
strcmp(grplc,grpLocation2) == 0)
found = index;
}
}
/*
if found == 0 (false) after the above search then we assume
that it is due to an inpromper updating of the GRPIDn and
GRPLCn keywords in the member header ==> nothing to delete
in the header. Else delete the GRPLCn and GRPIDn keywords
that identify the member HDU with the group HDU and
re-enumerate the remaining GRPIDn and GRPLCn keywords
*/
if(found != 0)
{
sprintf(keyword,"GRPID%d",found);
*status = fits_delete_key(mfptr,keyword,status);
sprintf(keyword,"GRPLC%d",found);
*status = fits_delete_key(mfptr,keyword,status);
*status = 0;
/* call fits_get_num_groups() to re-enumerate the GRPIDn */
*status = fits_get_num_groups(mfptr,&ngroups,status);
}
}
/*
finally, remove the member entry from the current grouping table
pointed to by gfptr
*/
*status = fits_delete_rows(gfptr,member,1,status);
}
else
{
*status = BAD_OPTION;
ffpmsg("Invalid value specified for the rmopt parameter (ffgmrm)");
}
}while(0);
if(mfptr != NULL)
{
fits_close_file(mfptr,status);
}
return(*status);
}
/*---------------------------------------------------------------------------
Grouping Table support functions
---------------------------------------------------------------------------*/
int ffgtgc(fitsfile *gfptr, /* pointer to the grouping table */
int *xtensionCol, /* column ID of the MEMBER_XTENSION column */
int *extnameCol, /* column ID of the MEMBER_NAME column */
int *extverCol, /* column ID of the MEMBER_VERSION column */
int *positionCol, /* column ID of the MEMBER_POSITION column */
int *locationCol, /* column ID of the MEMBER_LOCATION column */
int *uriCol, /* column ID of the MEMBER_URI_TYPE column */
int *grptype, /* group structure type code specifying the
grouping table columns that are defined:
GT_ID_ALL_URI (0) ==> all columns defined
GT_ID_REF (1) ==> reference cols only
GT_ID_POS (2) ==> position col only
GT_ID_ALL (3) ==> ref & pos cols
GT_ID_REF_URI (11) ==> ref & loc cols
GT_ID_POS_URI (12) ==> pos & loc cols */
int *status) /* return status code */
/*
examine the grouping table pointed to by gfptr and determine the column
index ID of each possible grouping column. If a column is not found then
an index of 0 is returned. the grptype parameter returns the structure
of the grouping table ==> what columns are defined.
*/
{
char keyvalue[FLEN_VALUE];
char comment[FLEN_COMMENT];
if(*status != 0) return(*status);
do
{
/*
if the HDU does not have an extname of "GROUPING" then it is not
a grouping table
*/
*status = fits_read_key_str(gfptr,"EXTNAME",keyvalue,comment,status);
if(*status == KEY_NO_EXIST)
{
*status = NOT_GROUP_TABLE;
ffpmsg("Specified HDU is not a Grouping Table (ffgtgc)");
}
if(*status != 0) continue;
prepare_keyvalue(keyvalue);
if(strcasecmp(keyvalue,"GROUPING") != 0)
{
*status = NOT_GROUP_TABLE;
continue;
}
/*
search for the MEMBER_XTENSION, MEMBER_NAME, MEMBER_VERSION,
MEMBER_POSITION, MEMBER_LOCATION and MEMBER_URI_TYPE columns
and determine their column index ID
*/
*status = fits_get_colnum(gfptr,CASESEN,"MEMBER_XTENSION",xtensionCol,
status);
if(*status == COL_NOT_FOUND)
{
*status = 0;
*xtensionCol = 0;
}
if(*status != 0) continue;
*status = fits_get_colnum(gfptr,CASESEN,"MEMBER_NAME",extnameCol,status);
if(*status == COL_NOT_FOUND)
{
*status = 0;
*extnameCol = 0;
}
if(*status != 0) continue;
*status = fits_get_colnum(gfptr,CASESEN,"MEMBER_VERSION",extverCol,
status);
if(*status == COL_NOT_FOUND)
{
*status = 0;
*extverCol = 0;
}
if(*status != 0) continue;
*status = fits_get_colnum(gfptr,CASESEN,"MEMBER_POSITION",positionCol,
status);
if(*status == COL_NOT_FOUND)
{
*status = 0;
*positionCol = 0;
}
if(*status != 0) continue;
*status = fits_get_colnum(gfptr,CASESEN,"MEMBER_LOCATION",locationCol,
status);
if(*status == COL_NOT_FOUND)
{
*status = 0;
*locationCol = 0;
}
if(*status != 0) continue;
*status = fits_get_colnum(gfptr,CASESEN,"MEMBER_URI_TYPE",uriCol,
status);
if(*status == COL_NOT_FOUND)
{
*status = 0;
*uriCol = 0;
}
if(*status != 0) continue;
/*
determine the type of grouping table structure used by this
grouping table and record it in the grptype parameter
*/
if(*xtensionCol && *extnameCol && *extverCol && *positionCol &&
*locationCol && *uriCol)
*grptype = GT_ID_ALL_URI;
else if(*xtensionCol && *extnameCol && *extverCol &&
*locationCol && *uriCol)
*grptype = GT_ID_REF_URI;
else if(*xtensionCol && *extnameCol && *extverCol && *positionCol)
*grptype = GT_ID_ALL;
else if(*xtensionCol && *extnameCol && *extverCol)
*grptype = GT_ID_REF;
else if(*positionCol && *locationCol && *uriCol)
*grptype = GT_ID_POS_URI;
else if(*positionCol)
*grptype = GT_ID_POS;
else
*status = NOT_GROUP_TABLE;
}while(0);
/*
if the table contained more than one column with a reserved name then
this cannot be considered a vailid grouping table
*/
if(*status == COL_NOT_UNIQUE)
{
*status = NOT_GROUP_TABLE;
ffpmsg("Specified HDU has multipule Group table cols defined (ffgtgc)");
}
return(*status);
}
/*****************************************************************************/
int ffgtdc(int grouptype, /* code specifying the type of
grouping table information:
GT_ID_ALL_URI 0 ==> defualt (all columns)
GT_ID_REF 1 ==> ID by reference
GT_ID_POS 2 ==> ID by position
GT_ID_ALL 3 ==> ID by ref. and position
GT_ID_REF_URI 11 ==> (1) + URI info
GT_ID_POS_URI 12 ==> (2) + URI info */
int xtensioncol, /* does MEMBER_XTENSION already exist? */
int extnamecol, /* does MEMBER_NAME aleady exist? */
int extvercol, /* does MEMBER_VERSION already exist? */
int positioncol, /* does MEMBER_POSITION already exist? */
int locationcol, /* does MEMBER_LOCATION already exist? */
int uricol, /* does MEMBER_URI_TYPE aleardy exist? */
char *ttype[], /* array of grouping table column TTYPE names
to define (if *col var false) */
char *tform[], /* array of grouping table column TFORM values
to define (if*col variable false) */
int *ncols, /* number of TTYPE and TFORM values returned */
int *status) /* return status code */
/*
create the TTYPE and TFORM values for the grouping table according to the
value of the grouptype parameter and the values of the *col flags. The
resulting TTYPE and TFORM are returned in ttype[] and tform[] respectively.
The number of TTYPE and TFORMs returned is given by ncols. Both the TTYPE[]
and TTFORM[] arrays must contain enough pre-allocated strings to hold
the returned information.
*/
{
int i = 0;
char xtension[] = "MEMBER_XTENSION";
char xtenTform[] = "8A";
char name[] = "MEMBER_NAME";
char nameTform[] = "32A";
char version[] = "MEMBER_VERSION";
char verTform[] = "1J";
char position[] = "MEMBER_POSITION";
char posTform[] = "1J";
char URI[] = "MEMBER_URI_TYPE";
char URITform[] = "3A";
char location[] = "MEMBER_LOCATION";
/* SPR 01720, move from 160A to 256A */
char locTform[] = "256A";
if(*status != 0) return(*status);
switch(grouptype)
{
case GT_ID_ALL_URI:
if(xtensioncol == 0)
{
strcpy(ttype[i],xtension);
strcpy(tform[i],xtenTform);
++i;
}
if(extnamecol == 0)
{
strcpy(ttype[i],name);
strcpy(tform[i],nameTform);
++i;
}
if(extvercol == 0)
{
strcpy(ttype[i],version);
strcpy(tform[i],verTform);
++i;
}
if(positioncol == 0)
{
strcpy(ttype[i],position);
strcpy(tform[i],posTform);
++i;
}
if(locationcol == 0)
{
strcpy(ttype[i],location);
strcpy(tform[i],locTform);
++i;
}
if(uricol == 0)
{
strcpy(ttype[i],URI);
strcpy(tform[i],URITform);
++i;
}
break;
case GT_ID_REF:
if(xtensioncol == 0)
{
strcpy(ttype[i],xtension);
strcpy(tform[i],xtenTform);
++i;
}
if(extnamecol == 0)
{
strcpy(ttype[i],name);
strcpy(tform[i],nameTform);
++i;
}
if(extvercol == 0)
{
strcpy(ttype[i],version);
strcpy(tform[i],verTform);
++i;
}
break;
case GT_ID_POS:
if(positioncol == 0)
{
strcpy(ttype[i],position);
strcpy(tform[i],posTform);
++i;
}
break;
case GT_ID_ALL:
if(xtensioncol == 0)
{
strcpy(ttype[i],xtension);
strcpy(tform[i],xtenTform);
++i;
}
if(extnamecol == 0)
{
strcpy(ttype[i],name);
strcpy(tform[i],nameTform);
++i;
}
if(extvercol == 0)
{
strcpy(ttype[i],version);
strcpy(tform[i],verTform);
++i;
}
if(positioncol == 0)
{
strcpy(ttype[i],position);
strcpy(tform[i], posTform);
++i;
}
break;
case GT_ID_REF_URI:
if(xtensioncol == 0)
{
strcpy(ttype[i],xtension);
strcpy(tform[i],xtenTform);
++i;
}
if(extnamecol == 0)
{
strcpy(ttype[i],name);
strcpy(tform[i],nameTform);
++i;
}
if(extvercol == 0)
{
strcpy(ttype[i],version);
strcpy(tform[i],verTform);
++i;
}
if(locationcol == 0)
{
strcpy(ttype[i],location);
strcpy(tform[i],locTform);
++i;
}
if(uricol == 0)
{
strcpy(ttype[i],URI);
strcpy(tform[i],URITform);
++i;
}
break;
case GT_ID_POS_URI:
if(positioncol == 0)
{
strcpy(ttype[i],position);
strcpy(tform[i],posTform);
++i;
}
if(locationcol == 0)
{
strcpy(ttype[i],location);
strcpy(tform[i],locTform);
++i;
}
if(uricol == 0)
{
strcpy(ttype[i],URI);
strcpy(tform[i],URITform);
++i;
}
break;
default:
*status = BAD_OPTION;
ffpmsg("Invalid value specified for the grouptype parameter (ffgtdc)");
break;
}
*ncols = i;
return(*status);
}
/*****************************************************************************/
int ffgmul(fitsfile *mfptr, /* pointer to the grouping table member HDU */
int rmopt, /* 0 ==> leave GRPIDn/GRPLCn keywords,
1 ==> remove GRPIDn/GRPLCn keywords */
int *status) /* return status code */
/*
examine all the GRPIDn and GRPLCn keywords in the member HDUs header
and remove the member from the grouping tables referenced; This
effectively "unlinks" the member from all of its groups. The rmopt
specifies if the GRPIDn/GRPLCn keywords are to be removed from the
member HDUs header after the unlinking.
*/
{
int memberPosition = 0;
int iomode;
long index;
long ngroups = 0;
long memberExtver = 0;
long memberID = 0;
char mbrLocation1[FLEN_FILENAME];
char mbrLocation2[FLEN_FILENAME];
char memberHDUtype[FLEN_VALUE];
char memberExtname[FLEN_VALUE];
char keyword[FLEN_KEYWORD];
char card[FLEN_CARD];
fitsfile *gfptr = NULL;
if(*status != 0) return(*status);
do
{
/*
determine location parameters of the member HDU; note that
default values are supplied if the expected keywords are not
found
*/
*status = fits_read_key_str(mfptr,"XTENSION",memberHDUtype,card,status);
if(*status == KEY_NO_EXIST)
{
strcpy(memberHDUtype,"PRIMARY");
*status = 0;
}
prepare_keyvalue(memberHDUtype);
*status = fits_read_key_lng(mfptr,"EXTVER",&memberExtver,card,status);
if(*status == KEY_NO_EXIST)
{
memberExtver = 1;
*status = 0;
}
*status = fits_read_key_str(mfptr,"EXTNAME",memberExtname,card,status);
if(*status == KEY_NO_EXIST)
{
memberExtname[0] = 0;
*status = 0;
}
prepare_keyvalue(memberExtname);
fits_get_hdu_num(mfptr,&memberPosition);
*status = fits_get_url(mfptr,mbrLocation1,mbrLocation2,NULL,NULL,
NULL,status);
if(*status != 0) continue;
/*
open each grouping table linked to this HDU and remove the member
from the grouping tables
*/
*status = fits_get_num_groups(mfptr,&ngroups,status);
/* loop over each group linked to the member HDU */
for(index = 1; index <= ngroups && *status == 0; ++index)
{
/* open the (index)th group linked to the member HDU */
*status = fits_open_group(mfptr,index,&gfptr,status);
/* if the group could not be opened then just skip it */
if(*status != 0)
{
*status = 0;
sprintf(card,"Cannot open the %dth group table (ffgmul)",
(int)index);
ffpmsg(card);
continue;
}
/*
make sure the grouping table can be modified before proceeding
*/
fits_file_mode(gfptr,&iomode,status);
if(iomode != READWRITE)
{
sprintf(card,"The %dth group cannot be modified (ffgtam)",
(int)index);
ffpmsg(card);
continue;
}
/*
try to find the member's row within the grouping table; first
try using the member HDU file's "real" URL string then try
using its originally opened URL string if either string exist
*/
memberID = 0;
if(strlen(mbrLocation1) != 0)
{
*status = ffgmf(gfptr,memberHDUtype,memberExtname,memberExtver,
memberPosition,mbrLocation1,&memberID,status);
}
if(*status == MEMBER_NOT_FOUND && strlen(mbrLocation2) != 0)
{
*status = 0;
*status = ffgmf(gfptr,memberHDUtype,memberExtname,memberExtver,
memberPosition,mbrLocation2,&memberID,status);
}
/* if the member was found then delete it from the grouping table */
if(*status == 0)
*status = fits_delete_rows(gfptr,memberID,1,status);
/*
continue the loop over all member groups even if an error
was generated
*/
if(*status == MEMBER_NOT_FOUND)
{
ffpmsg("cannot locate member's entry in group table (ffgmul)");
}
*status = 0;
/*
close the file pointed to by gfptr if it is non NULL to
prepare for the next loop iterration
*/
if(gfptr != NULL)
{
fits_close_file(gfptr,status);
gfptr = NULL;
}
}
if(*status != 0) continue;
/*
if rmopt is non-zero then find and delete the GRPIDn/GRPLCn
keywords from the member HDU header
*/
if(rmopt != 0)
{
fits_file_mode(mfptr,&iomode,status);
if(iomode == READONLY)
{
ffpmsg("Cannot modify member HDU, opened READONLY (ffgmul)");
continue;
}
/* delete all the GRPIDn/GRPLCn keywords */
for(index = 1; index <= ngroups && *status == 0; ++index)
{
sprintf(keyword,"GRPID%d",(int)index);
fits_delete_key(mfptr,keyword,status);
sprintf(keyword,"GRPLC%d",(int)index);
fits_delete_key(mfptr,keyword,status);
if(*status == KEY_NO_EXIST) *status = 0;
}
}
}while(0);
/* make sure the gfptr has been closed */
if(gfptr != NULL)
{
fits_close_file(gfptr,status);
}
return(*status);
}
/*--------------------------------------------------------------------------*/
int ffgmf(fitsfile *gfptr, /* pointer to grouping table HDU to search */
char *xtension, /* XTENSION value for member HDU */
char *extname, /* EXTNAME value for member HDU */
int extver, /* EXTVER value for member HDU */
int position, /* HDU position value for member HDU */
char *location, /* FITS file location value for member HDU */
long *member, /* member HDU ID within group table (if found) */
int *status) /* return status code */
/*
try to find the entry for the member HDU defined by the xtension, extname,
extver, position, and location parameters within the grouping table
pointed to by gfptr. If the member HDU is found then its ID (row number)
within the grouping table is returned in the member variable; if not
found then member is returned with a value of 0 and the status return
code will be set to MEMBER_NOT_FOUND.
Note that the member HDU postion information is used to obtain a member
match only if the grouping table type is GT_ID_POS_URI or GT_ID_POS. This
is because the position information can become invalid much more
easily then the reference information for a group member.
*/
{
int xtensionCol,extnameCol,extverCol,positionCol,locationCol,uriCol;
int mposition = 0;
int grptype;
int dummy;
int i;
long nmembers = 0;
long mextver = 0;
char charBuff1[FLEN_FILENAME];
char charBuff2[FLEN_FILENAME];
char tmpLocation[FLEN_FILENAME];
char mbrLocation1[FLEN_FILENAME];
char mbrLocation2[FLEN_FILENAME];
char mbrLocation3[FLEN_FILENAME];
char grpLocation1[FLEN_FILENAME];
char grpLocation2[FLEN_FILENAME];
char cwd[FLEN_FILENAME];
char nstr[] = {'\0'};
char *tmpPtr[2];
if(*status != 0) return(*status);
*member = 0;
tmpPtr[0] = charBuff1;
tmpPtr[1] = charBuff2;
if(*status != 0) return(*status);
/*
if the passed LOCATION value is not an absolute URL then turn it
into an absolute path
*/
if(location == NULL)
{
*tmpLocation = 0;
}
else if(*location == 0)
{
*tmpLocation = 0;
}
else if(!fits_is_url_absolute(location))
{
fits_path2url(location,tmpLocation,status);
if(*tmpLocation != '/')
{
fits_get_cwd(cwd,status);
strcat(cwd,"/");
strcat(cwd,tmpLocation);
fits_clean_url(cwd,tmpLocation,status);
}
}
else
strcpy(tmpLocation,location);
/*
retrieve the Grouping Convention reserved column positions within
the grouping table
*/
*status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol,
&locationCol,&uriCol,&grptype,status);
/* retrieve the number of group members */
*status = fits_get_num_members(gfptr,&nmembers,status);
/*
loop over all grouping table rows until the member HDU is found
*/
for(i = 1; i <= nmembers && *member == 0 && *status == 0; ++i)
{
if(xtensionCol != 0)
{
fits_read_col_str(gfptr,xtensionCol,i,1,1,nstr,tmpPtr,&dummy,status);
if(strcasecmp(tmpPtr[0],xtension) != 0) continue;
}
if(extnameCol != 0)
{
fits_read_col_str(gfptr,extnameCol,i,1,1,nstr,tmpPtr,&dummy,status);
if(strcasecmp(tmpPtr[0],extname) != 0) continue;
}
if(extverCol != 0)
{
fits_read_col_lng(gfptr,extverCol,i,1,1,0,
(long*)&mextver,&dummy,status);
if(extver != mextver) continue;
}
/* note we only use postionCol if we have to */
if(positionCol != 0 &&
(grptype == GT_ID_POS || grptype == GT_ID_POS_URI))
{
fits_read_col_int(gfptr,positionCol,i,1,1,0,
&mposition,&dummy,status);
if(position != mposition) continue;
}
/*
if no location string was passed to the function then assume that
the calling application does not wish to use it as a comparision
critera ==> if we got this far then we have a match
*/
if(location == NULL)
{
ffpmsg("NULL Location string given ==> ingore location (ffgmf)");
*member = i;
continue;
}
/*
if the grouping table MEMBER_LOCATION column exists then read the
location URL for the member, else set the location string to
a zero-length string for subsequent comparisions
*/
if(locationCol != 0)
{
fits_read_col_str(gfptr,locationCol,i,1,1,nstr,tmpPtr,&dummy,status);
strcpy(mbrLocation1,tmpPtr[0]);
*mbrLocation2 = 0;
}
else
*mbrLocation1 = 0;
/*
if the member location string from the grouping table is zero
length (either implicitly or explicitly) then assume that the
member HDU is in the same file as the grouping table HDU; retrieve
the possible URL values of the grouping table HDU file
*/
if(*mbrLocation1 == 0)
{
/* retrieve the possible URLs of the grouping table file */
*status = fits_get_url(gfptr,mbrLocation1,mbrLocation2,NULL,NULL,
NULL,status);
/* if non-NULL, make sure the first URL is absolute or a full path */
if(*mbrLocation1 != 0 && !fits_is_url_absolute(mbrLocation1) &&
*mbrLocation1 != '/')
{
fits_get_cwd(cwd,status);
strcat(cwd,"/");
strcat(cwd,mbrLocation1);
fits_clean_url(cwd,mbrLocation1,status);
}
/* if non-NULL, make sure the first URL is absolute or a full path */
if(*mbrLocation2 != 0 && !fits_is_url_absolute(mbrLocation2) &&
*mbrLocation2 != '/')
{
fits_get_cwd(cwd,status);
strcat(cwd,"/");
strcat(cwd,mbrLocation2);
fits_clean_url(cwd,mbrLocation2,status);
}
}
/*
if the member location was specified, then make sure that it is
either an absolute URL or specifies a full path
*/
else if(!fits_is_url_absolute(mbrLocation1) && *mbrLocation1 != '/')
{
strcpy(mbrLocation2,mbrLocation1);
/* get the possible URLs for the grouping table file */
*status = fits_get_url(gfptr,grpLocation1,grpLocation2,NULL,NULL,
NULL,status);
if(*grpLocation1 != 0)
{
/* make sure the first grouping table URL is absolute */
if(!fits_is_url_absolute(grpLocation1) && *grpLocation1 != '/')
{
fits_get_cwd(cwd,status);
strcat(cwd,"/");
strcat(cwd,grpLocation1);
fits_clean_url(cwd,grpLocation1,status);
}
/* create an absoute URL for the member */
fits_relurl2url(grpLocation1,mbrLocation1,mbrLocation3,status);
/*
if URL construction succeeded then copy it to the
first location string; else set the location string to
empty
*/
if(*status == 0)
{
strcpy(mbrLocation1,mbrLocation3);
}
else if(*status == URL_PARSE_ERROR)
{
*status = 0;
*mbrLocation1 = 0;
}
}
else
*mbrLocation1 = 0;
if(*grpLocation2 != 0)
{
/* make sure the second grouping table URL is absolute */
if(!fits_is_url_absolute(grpLocation2) && *grpLocation2 != '/')
{
fits_get_cwd(cwd,status);
strcat(cwd,"/");
strcat(cwd,grpLocation2);
fits_clean_url(cwd,grpLocation2,status);
}
/* create an absolute URL for the member */
fits_relurl2url(grpLocation2,mbrLocation2,mbrLocation3,status);
/*
if URL construction succeeded then copy it to the
second location string; else set the location string to
empty
*/
if(*status == 0)
{
strcpy(mbrLocation2,mbrLocation3);
}
else if(*status == URL_PARSE_ERROR)
{
*status = 0;
*mbrLocation2 = 0;
}
}
else
*mbrLocation2 = 0;
}
/*
compare the passed member HDU file location string with the
(possibly two) member location strings to see if there is a match
*/
if(strcmp(mbrLocation1,tmpLocation) != 0 &&
strcmp(mbrLocation2,tmpLocation) != 0 ) continue;
/* if we made it this far then a match to the member HDU was found */
*member = i;
}
/* if a match was not found then set the return status code */
if(*member == 0 && *status == 0)
{
*status = MEMBER_NOT_FOUND;
ffpmsg("Cannot find specified member HDU (ffgmf)");
}
return(*status);
}
/*--------------------------------------------------------------------------
Recursive Group Functions
--------------------------------------------------------------------------*/
int ffgtrmr(fitsfile *gfptr, /* FITS file pointer to group */
HDUtracker *HDU, /* list of processed HDUs */
int *status) /* return status code */
/*
recursively remove a grouping table and all its members. Each member of
the grouping table pointed to by gfptr it processed. If the member is itself
a grouping table then ffgtrmr() is recursively called to process all
of its members. The HDUtracker struct *HDU is used to make sure a member
is not processed twice, thus avoiding an infinite loop (e.g., a grouping
table contains itself as a member).
*/
{
int i;
int hdutype;
long nmembers = 0;
char keyvalue[FLEN_VALUE];
char comment[FLEN_COMMENT];
fitsfile *mfptr = NULL;
if(*status != 0) return(*status);
/* get the number of members contained by this grouping table */
*status = fits_get_num_members(gfptr,&nmembers,status);
/* loop over all group members and delete them */
for(i = nmembers; i > 0 && *status == 0; --i)
{
/* open the member HDU */
*status = fits_open_member(gfptr,i,&mfptr,status);
/* if the member cannot be opened then just skip it and continue */
if(*status == MEMBER_NOT_FOUND)
{
*status = 0;
continue;
}
/* Any other error is a reason to abort */
if(*status != 0) continue;
/* add the member HDU to the HDUtracker struct */
*status = fftsad(mfptr,HDU,NULL,NULL);
/* status == HDU_ALREADY_TRACKED ==> HDU has already been processed */
if(*status == HDU_ALREADY_TRACKED)
{
*status = 0;
fits_close_file(mfptr,status);
continue;
}
else if(*status != 0) continue;
/* determine if the member HDU is itself a grouping table */
*status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,comment,status);
/* if no EXTNAME is found then the HDU cannot be a grouping table */
if(*status == KEY_NO_EXIST)
{
*status = 0;
keyvalue[0] = 0;
}
prepare_keyvalue(keyvalue);
/* Any other error is a reason to abort */
if(*status != 0) continue;
/*
if the EXTNAME == GROUPING then the member is a grouping table
and we must call ffgtrmr() to process its members
*/
if(strcasecmp(keyvalue,"GROUPING") == 0)
*status = ffgtrmr(mfptr,HDU,status);
/*
unlink all the grouping tables that contain this HDU as a member
and then delete the HDU (if not a PHDU)
*/
if(fits_get_hdu_num(mfptr,&hdutype) == 1)
*status = ffgmul(mfptr,1,status);
else
{
*status = ffgmul(mfptr,0,status);
*status = fits_delete_hdu(mfptr,&hdutype,status);
}
/* close the fitsfile pointer */
fits_close_file(mfptr,status);
}
return(*status);
}
/*--------------------------------------------------------------------------*/
int ffgtcpr(fitsfile *infptr, /* input FITS file pointer */
fitsfile *outfptr, /* output FITS file pointer */
int cpopt, /* code specifying copy options:
OPT_GCP_GPT (0) ==> cp only grouping table
OPT_GCP_ALL (2) ==> recusrively copy
members and their members (if groups) */
HDUtracker *HDU, /* list of already copied HDUs */
int *status) /* return status code */
/*
copy a Group to a new FITS file. If the cpopt parameter is set to
OPT_GCP_GPT (copy grouping table only) then the existing members have their
GRPIDn and GRPLCn keywords updated to reflect the existance of the new group,
since they now belong to another group. If cpopt is set to OPT_GCP_ALL
(copy grouping table and members recursively) then the original members are
not updated; the new grouping table is modified to include only the copied
member HDUs and not the original members.
Note that this function is recursive. When copt is OPT_GCP_ALL it will call
itself whenever a member HDU of the current grouping table is itself a
grouping table (i.e., EXTNAME = 'GROUPING').
*/
{
int i;
int nexclude = 8;
int hdutype = 0;
int groupHDUnum = 0;
int numkeys = 0;
int keypos = 0;
int startSearch = 0;
int newPosition = 0;
long nmembers = 0;
long tfields = 0;
long newTfields = 0;
char keyword[FLEN_KEYWORD];
char keyvalue[FLEN_VALUE];
char card[FLEN_CARD];
char comment[FLEN_CARD];
char *tkeyvalue;
char *includeList[] = {"*"};
char *excludeList[] = {"EXTNAME","EXTVER","GRPNAME","GRPID#","GRPLC#",
"THEAP","TDIM#","T????#"};
fitsfile *mfptr = NULL;
if(*status != 0) return(*status);
do
{
/*
create a new grouping table in the FITS file pointed to by outptr
*/
*status = fits_get_num_members(infptr,&nmembers,status);
*status = fits_read_key_str(infptr,"GRPNAME",keyvalue,card,status);
if(*status == KEY_NO_EXIST)
{
keyvalue[0] = 0;
*status = 0;
}
prepare_keyvalue(keyvalue);
*status = fits_create_group(outfptr,keyvalue,GT_ID_ALL_URI,status);
/* save the new grouping table's HDU position for future use */
fits_get_hdu_num(outfptr,&groupHDUnum);
/* update the HDUtracker struct with the grouping table's new position */
*status = fftsud(infptr,HDU,groupHDUnum,NULL);
/*
Now populate the copied grouping table depending upon the
copy option parameter value
*/
switch(cpopt)
{
/*
for the "copy grouping table only" option we only have to
add the members of the original grouping table to the new
grouping table
*/
case OPT_GCP_GPT:
for(i = 1; i <= nmembers && *status == 0; ++i)
{
*status = fits_open_member(infptr,i,&mfptr,status);
*status = fits_add_group_member(outfptr,mfptr,0,status);
fits_close_file(mfptr,status);
mfptr = NULL;
}
break;
case OPT_GCP_ALL:
/*
for the "copy the entire group" option
*/
/* loop over all the grouping table members */
for(i = 1; i <= nmembers && *status == 0; ++i)
{
/* open the ith member */
*status = fits_open_member(infptr,i,&mfptr,status);
if(*status != 0) continue;
/* add it to the HDUtracker struct */
*status = fftsad(mfptr,HDU,&newPosition,NULL);
/* if already copied then just add the member to the group */
if(*status == HDU_ALREADY_TRACKED)
{
*status = 0;
*status = fits_add_group_member(outfptr,NULL,newPosition,
status);
fits_close_file(mfptr,status);
mfptr = NULL;
continue;
}
else if(*status != 0) continue;
/* see if the member is a grouping table */
*status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,card,
status);
if(*status == KEY_NO_EXIST)
{
keyvalue[0] = 0;
*status = 0;
}
prepare_keyvalue(keyvalue);
/*
if the member is a grouping table then copy it and all of
its members using ffgtcpr(), else copy it using
fits_copy_member(); the outptr will point to the newly
copied member upon return from both functions
*/
if(strcasecmp(keyvalue,"GROUPING") == 0)
*status = ffgtcpr(mfptr,outfptr,OPT_GCP_ALL,HDU,status);
else
*status = fits_copy_member(infptr,outfptr,i,OPT_MCP_NADD,
status);
/* retrieve the position of the newly copied member */
fits_get_hdu_num(outfptr,&newPosition);
/* update the HDUtracker struct with member's new position */
if(strcasecmp(keyvalue,"GROUPING") != 0)
*status = fftsud(mfptr,HDU,newPosition,NULL);
/* move the outfptr back to the copied grouping table HDU */
*status = fits_movabs_hdu(outfptr,groupHDUnum,&hdutype,status);
/* add the copied member HDU to the copied grouping table */
*status = fits_add_group_member(outfptr,NULL,newPosition,status);
/* close the mfptr pointer */
fits_close_file(mfptr,status);
mfptr = NULL;
}
break;
default:
*status = BAD_OPTION;
ffpmsg("Invalid value specified for cmopt parameter (ffgtcpr)");
break;
}
if(*status != 0) continue;
/*
reposition the outfptr to the grouping table so that the grouping
table is the CHDU upon return to the calling function
*/
fits_movabs_hdu(outfptr,groupHDUnum,&hdutype,status);
/*
copy all auxiliary keyword records from the original grouping table
to the new grouping table; they are copied in their original order
and inserted just before the TTYPE1 keyword record
*/
*status = fits_read_card(outfptr,"TTYPE1",card,status);
*status = fits_get_hdrpos(outfptr,&numkeys,&keypos,status);
--keypos;
startSearch = 8;
while(*status == 0)
{
ffgrec(infptr,startSearch,card,status);
*status = fits_find_nextkey(infptr,includeList,1,excludeList,
nexclude,card,status);
*status = fits_get_hdrpos(infptr,&numkeys,&startSearch,status);
--startSearch;
/* SPR 1738 */
if (strncmp(card,"GRPLC",5)) {
/* Not going to be a long string so we're ok */
*status = fits_insert_record(outfptr,keypos,card,status);
} else {
/* We could have a long string */
*status = fits_read_record(infptr,startSearch,card,status);
card[9] = '\0';
*status = fits_read_key_longstr(infptr,card,&tkeyvalue,comment,
status);
if (0 == *status) {
fits_insert_key_longstr(outfptr,card,tkeyvalue,comment,status);
fits_write_key_longwarn(outfptr,status);
free(tkeyvalue);
}
}
++keypos;
}
if(*status == KEY_NO_EXIST)
*status = 0;
else if(*status != 0) continue;
/*
search all the columns of the original grouping table and copy
those to the new grouping table that were not part of the grouping
convention. Note that is legal to have additional columns in a
grouping table. Also note that the order of the columns may
not be the same in the original and copied grouping table.
*/
/* retrieve the number of columns in the original and new group tables */
*status = fits_read_key_lng(infptr,"TFIELDS",&tfields,card,status);
*status = fits_read_key_lng(outfptr,"TFIELDS",&newTfields,card,status);
for(i = 1; i <= tfields; ++i)
{
sprintf(keyword,"TTYPE%d",i);
*status = fits_read_key_str(infptr,keyword,keyvalue,card,status);
if(*status == KEY_NO_EXIST)
{
*status = 0;
keyvalue[0] = 0;
}
prepare_keyvalue(keyvalue);
if(strcasecmp(keyvalue,"MEMBER_XTENSION") != 0 &&
strcasecmp(keyvalue,"MEMBER_NAME") != 0 &&
strcasecmp(keyvalue,"MEMBER_VERSION") != 0 &&
strcasecmp(keyvalue,"MEMBER_POSITION") != 0 &&
strcasecmp(keyvalue,"MEMBER_LOCATION") != 0 &&
strcasecmp(keyvalue,"MEMBER_URI_TYPE") != 0 )
{
/* SPR 3956, add at the end of the table */
*status = fits_copy_col(infptr,outfptr,i,newTfields+1,1,status);
++newTfields;
}
}
}while(0);
if(mfptr != NULL)
{
fits_close_file(mfptr,status);
}
return(*status);
}
/*--------------------------------------------------------------------------
HDUtracker struct manipulation functions
--------------------------------------------------------------------------*/
int fftsad(fitsfile *mfptr, /* pointer to an member HDU */
HDUtracker *HDU, /* pointer to an HDU tracker struct */
int *newPosition, /* new HDU position of the member HDU */
char *newFileName) /* file containing member HDU */
/*
add an HDU to the HDUtracker struct pointed to by HDU. The HDU is only
added if it does not already reside in the HDUtracker. If it already
resides in the HDUtracker then the new HDU postion and file name are
returned in newPosition and newFileName (if != NULL)
*/
{
int i;
int hdunum;
int status = 0;
char filename1[FLEN_FILENAME];
char filename2[FLEN_FILENAME];
do
{
/* retrieve the HDU's position within the FITS file */
fits_get_hdu_num(mfptr,&hdunum);
/* retrieve the HDU's file name */
status = fits_file_name(mfptr,filename1,&status);
/* parse the file name and construct the "standard" URL for it */
status = ffrtnm(filename1,filename2,&status);
/*
examine all the existing HDUs in the HDUtracker an see if this HDU
has already been registered
*/
for(i = 0;
i < HDU->nHDU && !(HDU->position[i] == hdunum
&& strcmp(HDU->filename[i],filename2) == 0);
++i);
if(i != HDU->nHDU)
{
status = HDU_ALREADY_TRACKED;
if(newPosition != NULL) *newPosition = HDU->newPosition[i];
if(newFileName != NULL) strcpy(newFileName,HDU->newFilename[i]);
continue;
}
if(HDU->nHDU == MAX_HDU_TRACKER)
{
status = TOO_MANY_HDUS_TRACKED;
continue;
}
HDU->filename[i] = (char*) malloc(FLEN_FILENAME * sizeof(char));
if(HDU->filename[i] == NULL)
{
status = MEMORY_ALLOCATION;
continue;
}
HDU->newFilename[i] = (char*) malloc(FLEN_FILENAME * sizeof(char));
if(HDU->newFilename[i] == NULL)
{
status = MEMORY_ALLOCATION;
free(HDU->filename[i]);
continue;
}
HDU->position[i] = hdunum;
HDU->newPosition[i] = hdunum;
strcpy(HDU->filename[i],filename2);
strcpy(HDU->newFilename[i],filename2);
++(HDU->nHDU);
}while(0);
return(status);
}
/*--------------------------------------------------------------------------*/
int fftsud(fitsfile *mfptr, /* pointer to an member HDU */
HDUtracker *HDU, /* pointer to an HDU tracker struct */
int newPosition, /* new HDU position of the member HDU */
char *newFileName) /* file containing member HDU */
/*
update the HDU information in the HDUtracker struct pointed to by HDU. The
HDU to update is pointed to by mfptr. If non-zero, the value of newPosition
is used to update the HDU->newPosition[] value for the mfptr, and if
non-NULL the newFileName value is used to update the HDU->newFilename[]
value for mfptr.
*/
{
int i;
int hdunum;
int status = 0;
char filename1[FLEN_FILENAME];
char filename2[FLEN_FILENAME];
/* retrieve the HDU's position within the FITS file */
fits_get_hdu_num(mfptr,&hdunum);
/* retrieve the HDU's file name */
status = fits_file_name(mfptr,filename1,&status);
/* parse the file name and construct the "standard" URL for it */
status = ffrtnm(filename1,filename2,&status);
/*
examine all the existing HDUs in the HDUtracker an see if this HDU
has already been registered
*/
for(i = 0; i < HDU->nHDU &&
!(HDU->position[i] == hdunum && strcmp(HDU->filename[i],filename2) == 0);
++i);
/* if previously registered then change newPosition and newFileName */
if(i != HDU->nHDU)
{
if(newPosition != 0) HDU->newPosition[i] = newPosition;
if(newFileName != NULL)
{
strcpy(HDU->newFilename[i],newFileName);
}
}
else
status = MEMBER_NOT_FOUND;
return(status);
}
/*---------------------------------------------------------------------------*/
void prepare_keyvalue(char *keyvalue) /* string containing keyword value */
/*
strip off all single quote characters "'" and blank spaces from a keyword
value retrieved via fits_read_key*() routines
this is necessary so that a standard comparision of keyword values may
be made
*/
{
int i;
int length;
/*
strip off any leading or trailing single quotes (`) and (') from
the keyword value
*/
length = strlen(keyvalue) - 1;
if(keyvalue[0] == '\'' && keyvalue[length] == '\'')
{
for(i = 0; i < length - 1; ++i) keyvalue[i] = keyvalue[i+1];
keyvalue[length-1] = 0;
}
/*
strip off any trailing blanks from the keyword value; note that if the
keyvalue consists of nothing but blanks then no blanks are stripped
*/
length = strlen(keyvalue) - 1;
for(i = 0; i < length && keyvalue[i] == ' '; ++i);
if(i != length)
{
for(i = length; i >= 0 && keyvalue[i] == ' '; --i) keyvalue[i] = '\0';
}
}
/*---------------------------------------------------------------------------
Host dependent directory path to/from URL functions
--------------------------------------------------------------------------*/
int fits_path2url(char *inpath, /* input file path string */
char *outpath, /* output file path string */
int *status)
/*
convert a file path into its Unix-style equivelent for URL
purposes. Note that this process is platform dependent. This
function supports Unix, MSDOS/WIN32, VMS and Macintosh platforms.
The plaform dependant code is conditionally compiled depending upon
the setting of the appropriate C preprocessor macros.
*/
{
char buff[FLEN_FILENAME];
#if defined(WINNT) || defined(__WINNT__)
/*
Microsoft Windows NT case. We assume input file paths of the form:
//disk/path/filename
All path segments may be null, so that a single file name is the
simplist case.
The leading "//" becomes a single "/" if present. If no "//" is present,
then make sure the resulting URL path is relative, i.e., does not
begin with a "/". In other words, the only way that an absolute URL
file path may be generated is if the drive specification is given.
*/
if(*status > 0) return(*status);
if(inpath[0] == '/')
{
strcpy(buff,inpath+1);
}
else
{
strcpy(buff,inpath);
}
#elif defined(MSDOS) || defined(__WIN32__) || defined(WIN32)
/*
MSDOS or Microsoft windows/NT case. The assumed form of the
input path is:
disk:\path\filename
All path segments may be null, so that a single file name is the
simplist case.
All back-slashes '\' become slashes '/'; if the path starts with a
string of the form "X:" then it is replaced with "/X/"
*/
int i,j,k;
int size;
if(*status > 0) return(*status);
for(i = 0, j = 0, size = strlen(inpath), buff[0] = 0;
i < size; j = strlen(buff))
{
switch(inpath[i])
{
case ':':
/*
must be a disk desiginator; add a slash '/' at the start of
outpath to designate that the path is absolute, then change
the colon ':' to a slash '/'
*/
for(k = j; k >= 0; --k) buff[k+1] = buff[k];
buff[0] = '/';
strcat(buff,"/");
++i;
break;
case '\\':
/* just replace the '\' with a '/' IF its not the first character */
if(i != 0 && buff[(j == 0 ? 0 : j-1)] != '/')
{
buff[j] = '/';
buff[j+1] = 0;
}
++i;
break;
default:
/* copy the character from inpath to buff as is */
buff[j] = inpath[i];
buff[j+1] = 0;
++i;
break;
}
}
#elif defined(VMS) || defined(vms) || defined(__vms)
/*
VMS case. Assumed format of the input path is:
node::disk:[path]filename.ext;version
Any part of the file path may be missing, so that in the simplist
case a single file name/extension is given.
all brackets "[", "]" and dots "." become "/"; dashes "-" become "..",
all single colons ":" become ":/", all double colons "::" become
"FILE://"
*/
int i,j,k;
int done;
int size;
if(*status > 0) return(*status);
/* see if inpath contains a directory specification */
if(strchr(inpath,']') == NULL)
done = 1;
else
done = 0;
for(i = 0, j = 0, size = strlen(inpath), buff[0] = 0;
i < size && j < FLEN_FILENAME - 8; j = strlen(buff))
{
switch(inpath[i])
{
case ':':
/*
must be a logical/symbol separator or (in the case of a double
colon "::") machine node separator
*/
if(inpath[i+1] == ':')
{
/* insert a "FILE://" at the start of buff ==> machine given */
for(k = j; k >= 0; --k) buff[k+7] = buff[k];
strncpy(buff,"FILE://",7);
i += 2;
}
else if(strstr(buff,"FILE://") == NULL)
{
/* insert a "/" at the start of buff ==> absolute path */
for(k = j; k >= 0; --k) buff[k+1] = buff[k];
buff[0] = '/';
++i;
}
else
++i;
/* a colon always ==> path separator */
strcat(buff,"/");
break;
case ']':
/* end of directory spec, file name spec begins after this */
done = 1;
buff[j] = '/';
buff[j+1] = 0;
++i;
break;
case '[':
/*
begin directory specification; add a '/' only if the last char
is not '/'
*/
if(i != 0 && buff[(j == 0 ? 0 : j-1)] != '/')
{
buff[j] = '/';
buff[j+1] = 0;
}
++i;
break;
case '.':
/*
directory segment separator or file name/extension separator;
we decide which by looking at the value of done
*/
if(!done)
{
/* must be a directory segment separator */
if(inpath[i-1] == '[')
{
strcat(buff,"./");
++j;
}
else
buff[j] = '/';
}
else
/* must be a filename/extension separator */
buff[j] = '.';
buff[j+1] = 0;
++i;
break;
case '-':
/*
a dash is the same as ".." in Unix speak, but lets make sure
that its not part of the file name first!
*/
if(!done)
/* must be part of the directory path specification */
strcat(buff,"..");
else
{
/* the dash is part of the filename, so just copy it as is */
buff[j] = '-';
buff[j+1] = 0;
}
++i;
break;
default:
/* nothing special, just copy the character as is */
buff[j] = inpath[i];
buff[j+1] = 0;
++i;
break;
}
}
if(j > FLEN_FILENAME - 8)
{
*status = URL_PARSE_ERROR;
ffpmsg("resulting path to URL conversion too big (fits_path2url)");
}
#elif defined(macintosh)
/*
MacOS case. The assumed form of the input path is:
disk:path:filename
It is assumed that all paths are absolute with disk and path specified,
unless no colons ":" are supplied with the string ==> a single file name
only. All colons ":" become slashes "/", and if one or more colon is
encountered then the path is specified as absolute.
*/
int i,j,k;
int firstColon;
int size;
if(*status > 0) return(*status);
for(i = 0, j = 0, firstColon = 1, size = strlen(inpath), buff[0] = 0;
i < size; j = strlen(buff))
{
switch(inpath[i])
{
case ':':
/*
colons imply path separators. If its the first colon encountered
then assume that its the disk designator and add a slash to the
beginning of the buff string
*/
if(firstColon)
{
firstColon = 0;
for(k = j; k >= 0; --k) buff[k+1] = buff[k];
buff[0] = '/';
}
/* all colons become slashes */
strcat(buff,"/");
++i;
break;
default:
/* copy the character from inpath to buff as is */
buff[j] = inpath[i];
buff[j+1] = 0;
++i;
break;
}
}
#else
/*
Default Unix case.
Nothing special to do here except to remove the double or more // and
replace them with single /
*/
int ii = 0;
int jj = 0;
if(*status > 0) return(*status);
while (inpath[ii]) {
if (inpath[ii] == '/' && inpath[ii+1] == '/') {
/* do nothing */
} else {
buff[jj] = inpath[ii];
jj++;
}
ii++;
}
buff[jj] = '\0';
/* printf("buff is %s\ninpath is %s\n",buff,inpath); */
/* strcpy(buff,inpath); */
#endif
/*
encode all "unsafe" and "reserved" URL characters
*/
*status = fits_encode_url(buff,outpath,status);
return(*status);
}
/*---------------------------------------------------------------------------*/
int fits_url2path(char *inpath, /* input file path string */
char *outpath, /* output file path string */
int *status)
/*
convert a Unix-style URL into a platform dependent directory path.
Note that this process is platform dependent. This
function supports Unix, MSDOS/WIN32, VMS and Macintosh platforms. Each
platform dependent code segment is conditionally compiled depending
upon the setting of the appropriate C preprocesser macros.
*/
{
char buff[FLEN_FILENAME];
int absolute;
#if defined(MSDOS) || defined(__WIN32__) || defined(WIN32)
char *tmpStr;
#elif defined(VMS) || defined(vms) || defined(__vms)
int i;
char *tmpStr;
#elif defined(macintosh)
char *tmpStr;
#endif
if(*status != 0) return(*status);
/*
make a copy of the inpath so that we can manipulate it
*/
strcpy(buff,inpath);
/*
convert any encoded characters to their unencoded values
*/
*status = fits_unencode_url(inpath,buff,status);
/*
see if the URL is given as absolute w.r.t. the "local" file system
*/
if(buff[0] == '/')
absolute = 1;
else
absolute = 0;
#if defined(WINNT) || defined(__WINNT__)
/*
Microsoft Windows NT case. We create output paths of the form
//disk/path/filename
All path segments but the last may be null, so that a single file name
is the simplist case.
*/
if(absolute)
{
strcpy(outpath,"/");
strcat(outpath,buff);
}
else
{
strcpy(outpath,buff);
}
#elif defined(MSDOS) || defined(__WIN32__) || defined(WIN32)
/*
MSDOS or Microsoft windows/NT case. The output path will be of the
form
disk:\path\filename
All path segments but the last may be null, so that a single file name
is the simplist case.
*/
/*
separate the URL into tokens at each slash '/' and process until
all tokens have been examined
*/
for(tmpStr = strtok(buff,"/"), outpath[0] = 0;
tmpStr != NULL; tmpStr = strtok(NULL,"/"))
{
strcat(outpath,tmpStr);
/*
if the absolute flag is set then process the token as a disk
specification; else just process it as a directory path or filename
*/
if(absolute)
{
strcat(outpath,":\\");
absolute = 0;
}
else
strcat(outpath,"\\");
}
/* remove the last "\" from the outpath, it does not belong there */
outpath[strlen(outpath)-1] = 0;
#elif defined(VMS) || defined(vms) || defined(__vms)
/*
VMS case. The output path will be of the form:
node::disk:[path]filename.ext;version
Any part of the file path may be missing execpt filename.ext, so that in
the simplist case a single file name/extension is given.
if the path is specified as relative starting with "./" then the first
part of the VMS path is "[.". If the path is relative and does not start
with "./" (e.g., "a/b/c") then the VMS path is constructed as
"[a.b.c]"
*/
/*
separate the URL into tokens at each slash '/' and process until
all tokens have been examined
*/
for(tmpStr = strtok(buff,"/"), outpath[0] = 0;
tmpStr != NULL; tmpStr = strtok(NULL,"/"))
{
if(strcasecmp(tmpStr,"FILE:") == 0)
{
/* the next token should contain the DECnet machine name */
tmpStr = strtok(NULL,"/");
if(tmpStr == NULL) continue;
strcat(outpath,tmpStr);
strcat(outpath,"::");
/* set the absolute flag to true for the next token */
absolute = 1;
}
else if(strcmp(tmpStr,"..") == 0)
{
/* replace all Unix-like ".." with VMS "-" */
if(strlen(outpath) == 0) strcat(outpath,"[");
strcat(outpath,"-.");
}
else if(strcmp(tmpStr,".") == 0 && strlen(outpath) == 0)
{
/*
must indicate a relative path specifier
*/
strcat(outpath,"[.");
}
else if(strchr(tmpStr,'.') != NULL)
{
/*
must be up to the file name; turn the last "." path separator
into a "]" and then add the file name to the outpath
*/
i = strlen(outpath);
if(i > 0 && outpath[i-1] == '.') outpath[i-1] = ']';
strcat(outpath,tmpStr);
}
else
{
/*
process the token as a a directory path segement
*/
if(absolute)
{
/* treat the token as a disk specifier */
absolute = 0;
strcat(outpath,tmpStr);
strcat(outpath,":[");
}
else if(strlen(outpath) == 0)
{
/* treat the token as the first directory path specifier */
strcat(outpath,"[");
strcat(outpath,tmpStr);
strcat(outpath,".");
}
else
{
/* treat the token as an imtermediate path specifier */
strcat(outpath,tmpStr);
strcat(outpath,".");
}
}
}
#elif defined(macintosh)
/*
MacOS case. The output path will be of the form
disk:path:filename
All path segments but the last may be null, so that a single file name
is the simplist case.
*/
/*
separate the URL into tokens at each slash '/' and process until
all tokens have been examined
*/
for(tmpStr = strtok(buff,"/"), outpath[0] = 0;
tmpStr != NULL; tmpStr = strtok(NULL,"/"))
{
strcat(outpath,tmpStr);
strcat(outpath,":");
}
/* remove the last ":" from the outpath, it does not belong there */
outpath[strlen(outpath)-1] = 0;
#else
/*
Default Unix case.
Nothing special to do here
*/
strcpy(outpath,buff);
#endif
return(*status);
}
/****************************************************************************/
int fits_get_cwd(char *cwd, /* IO current working directory string */
int *status)
/*
retrieve the string containing the current working directory absolute
path in Unix-like URL standard notation. It is assumed that the CWD
string has a size of at least FLEN_FILENAME.
Note that this process is platform dependent. This
function supports Unix, MSDOS/WIN32, VMS and Macintosh platforms. Each
platform dependent code segment is conditionally compiled depending
upon the setting of the appropriate C preprocesser macros.
*/
{
char buff[FLEN_FILENAME];
if(*status != 0) return(*status);
#if defined(macintosh)
/*
MacOS case. Currently unknown !!!!
*/
*buff = 0;
#else
/*
Good old getcwd() seems to work with all other platforms
*/
getcwd(buff,FLEN_FILENAME);
#endif
/*
convert the cwd string to a URL standard path string
*/
fits_path2url(buff,cwd,status);
return(*status);
}
/*---------------------------------------------------------------------------*/
int fits_get_url(fitsfile *fptr, /* I ptr to FITS file to evaluate */
char *realURL, /* O URL of real FITS file */
char *startURL, /* O URL of starting FITS file */
char *realAccess, /* O true access method of FITS file */
char *startAccess,/* O "official" access of FITS file */
int *iostate, /* O can this file be modified? */
int *status)
/*
For grouping convention purposes, determine the URL of the FITS file
associated with the fitsfile pointer fptr. The true access type (file://,
mem://, shmem://, root://), starting "official" access type, and iostate
(0 ==> readonly, 1 ==> readwrite) are also returned.
It is assumed that the url string has enough room to hold the resulting
URL, and the the accessType string has enough room to hold the access type.
*/
{
int i;
int tmpIOstate = 0;
char infile[FLEN_FILENAME];
char outfile[FLEN_FILENAME];
char tmpStr1[FLEN_FILENAME];
char tmpStr2[FLEN_FILENAME];
char tmpStr3[FLEN_FILENAME];
char tmpStr4[FLEN_FILENAME];
char *tmpPtr;
if(*status != 0) return(*status);
do
{
/*
retrieve the member HDU's file name as opened by ffopen()
and parse it into its constitutent pieces; get the currently
active driver token too
*/
*tmpStr1 = *tmpStr2 = *tmpStr3 = *tmpStr4 = 0;
*status = fits_file_name(fptr,tmpStr1,status);
*status = ffiurl(tmpStr1,NULL,infile,outfile,NULL,tmpStr2,tmpStr3,
tmpStr4,status);
if((*tmpStr2) || (*tmpStr3) || (*tmpStr4)) tmpIOstate = -1;
*status = ffurlt(fptr,tmpStr3,status);
strcpy(tmpStr4,tmpStr3);
*status = ffrtnm(tmpStr1,tmpStr2,status);
strcpy(tmpStr1,tmpStr2);
/*
for grouping convention purposes (only) determine the URL of the
actual FITS file being used for the given fptr, its true access
type (file://, mem://, shmem://, root://) and its iostate (0 ==>
read only, 1 ==> readwrite)
*/
/*
The first set of access types are "simple" in that they do not
use any redirection to temporary memory or outfiles
*/
/* standard disk file driver is in use */
if(strcasecmp(tmpStr3,"file://") == 0)
{
tmpIOstate = 1;
if(strlen(outfile)) strcpy(tmpStr1,outfile);
else *tmpStr2 = 0;
/*
make sure no FILE:// specifier is given in the tmpStr1
or tmpStr2 strings; the convention calls for local files
to have no access specification
*/
if((tmpPtr = strstr(tmpStr1,"://")) != NULL)
{
strcpy(infile,tmpPtr+3);
strcpy(tmpStr1,infile);
}
if((tmpPtr = strstr(tmpStr2,"://")) != NULL)
{
strcpy(infile,tmpPtr+3);
strcpy(tmpStr2,infile);
}
}
/* file stored in conventional memory */
else if(strcasecmp(tmpStr3,"mem://") == 0)
{
if(tmpIOstate < 0)
{
/* file is a temp mem file only */
ffpmsg("cannot make URL from temp MEM:// file (fits_get_url)");
*status = URL_PARSE_ERROR;
}
else
{
/* file is a "perminate" mem file for this process */
tmpIOstate = 1;
*tmpStr2 = 0;
}
}
/* file stored in conventional memory */
else if(strcasecmp(tmpStr3,"memkeep://") == 0)
{
strcpy(tmpStr3,"mem://");
*tmpStr4 = 0;
*tmpStr2 = 0;
tmpIOstate = 1;
}
/* file residing in shared memory */
else if(strcasecmp(tmpStr3,"shmem://") == 0)
{
*tmpStr4 = 0;
*tmpStr2 = 0;
tmpIOstate = 1;
}
/* file accessed via the ROOT network protocol */
else if(strcasecmp(tmpStr3,"root://") == 0)
{
*tmpStr4 = 0;
*tmpStr2 = 0;
tmpIOstate = 1;
}
/*
the next set of access types redirect the contents of the original
file to an special outfile because the original could not be
directly modified (i.e., resides on the network, was compressed).
In these cases the URL string takes on the value of the OUTFILE,
the access type becomes file://, and the iostate is set to 1 (can
read/write to the file).
*/
/* compressed file uncompressed and written to disk */
else if(strcasecmp(tmpStr3,"compressfile://") == 0)
{
strcpy(tmpStr1,outfile);
strcpy(tmpStr2,infile);
strcpy(tmpStr3,"file://");
strcpy(tmpStr4,"file://");
tmpIOstate = 1;
}
/* HTTP accessed file written locally to disk */
else if(strcasecmp(tmpStr3,"httpfile://") == 0)
{
strcpy(tmpStr1,outfile);
strcpy(tmpStr3,"file://");
strcpy(tmpStr4,"http://");
tmpIOstate = 1;
}
/* FTP accessd file written locally to disk */
else if(strcasecmp(tmpStr3,"ftpfile://") == 0)
{
strcpy(tmpStr1,outfile);
strcpy(tmpStr3,"file://");
strcpy(tmpStr4,"ftp://");
tmpIOstate = 1;
}
/* file from STDIN written to disk */
else if(strcasecmp(tmpStr3,"stdinfile://") == 0)
{
strcpy(tmpStr1,outfile);
strcpy(tmpStr3,"file://");
strcpy(tmpStr4,"stdin://");
tmpIOstate = 1;
}
/*
the following access types use memory resident files as temporary
storage; they cannot be modified or be made group members for
grouping conventions purposes, but their original files can be.
Thus, their tmpStr3s are reset to mem://, their iostate
values are set to 0 (for no-modification), and their URL string
values remain set to their original values
*/
/* compressed disk file uncompressed into memory */
else if(strcasecmp(tmpStr3,"compress://") == 0)
{
*tmpStr1 = 0;
strcpy(tmpStr2,infile);
strcpy(tmpStr3,"mem://");
strcpy(tmpStr4,"file://");
tmpIOstate = 0;
}
/* HTTP accessed file transferred into memory */
else if(strcasecmp(tmpStr3,"http://") == 0)
{
*tmpStr1 = 0;
strcpy(tmpStr3,"mem://");
strcpy(tmpStr4,"http://");
tmpIOstate = 0;
}
/* HTTP accessed compressed file transferred into memory */
else if(strcasecmp(tmpStr3,"httpcompress://") == 0)
{
*tmpStr1 = 0;
strcpy(tmpStr3,"mem://");
strcpy(tmpStr4,"http://");
tmpIOstate = 0;
}
/* FTP accessed file transferred into memory */
else if(strcasecmp(tmpStr3,"ftp://") == 0)
{
*tmpStr1 = 0;
strcpy(tmpStr3,"mem://");
strcpy(tmpStr4,"ftp://");
tmpIOstate = 0;
}
/* FTP accessed compressed file transferred into memory */
else if(strcasecmp(tmpStr3,"ftpcompress://") == 0)
{
*tmpStr1 = 0;
strcpy(tmpStr3,"mem://");
strcpy(tmpStr4,"ftp://");
tmpIOstate = 0;
}
/*
The last set of access types cannot be used to make a meaningful URL
strings from; thus an error is generated
*/
else if(strcasecmp(tmpStr3,"stdin://") == 0)
{
*status = URL_PARSE_ERROR;
ffpmsg("cannot make vaild URL from stdin:// (fits_get_url)");
*tmpStr1 = *tmpStr2 = 0;
}
else if(strcasecmp(tmpStr3,"stdout://") == 0)
{
*status = URL_PARSE_ERROR;
ffpmsg("cannot make vaild URL from stdout:// (fits_get_url)");
*tmpStr1 = *tmpStr2 = 0;
}
else if(strcasecmp(tmpStr3,"irafmem://") == 0)
{
*status = URL_PARSE_ERROR;
ffpmsg("cannot make vaild URL from irafmem:// (fits_get_url)");
*tmpStr1 = *tmpStr2 = 0;
}
if(*status != 0) continue;
/*
assign values to the calling parameters if they are non-NULL
*/
if(realURL != NULL)
{
if(strlen(tmpStr1) == 0)
*realURL = 0;
else
{
if((tmpPtr = strstr(tmpStr1,"://")) != NULL)
{
tmpPtr += 3;
i = (long)tmpPtr - (long)tmpStr1;
strncpy(realURL,tmpStr1,i);
}
else
{
tmpPtr = tmpStr1;
i = 0;
}
*status = fits_path2url(tmpPtr,realURL+i,status);
}
}
if(startURL != NULL)
{
if(strlen(tmpStr2) == 0)
*startURL = 0;
else
{
if((tmpPtr = strstr(tmpStr2,"://")) != NULL)
{
tmpPtr += 3;
i = (long)tmpPtr - (long)tmpStr2;
strncpy(startURL,tmpStr2,i);
}
else
{
tmpPtr = tmpStr2;
i = 0;
}
*status = fits_path2url(tmpPtr,startURL+i,status);
}
}
if(realAccess != NULL) strcpy(realAccess,tmpStr3);
if(startAccess != NULL) strcpy(startAccess,tmpStr4);
if(iostate != NULL) *iostate = tmpIOstate;
}while(0);
return(*status);
}
/*--------------------------------------------------------------------------
URL parse support functions
--------------------------------------------------------------------------*/
/* simple push/pop/shift/unshift string stack for use by fits_clean_url */
typedef char* grp_stack_data; /* type of data held by grp_stack */
typedef struct grp_stack_item_struct {
grp_stack_data data; /* value of this stack item */
struct grp_stack_item_struct* next; /* next stack item */
struct grp_stack_item_struct* prev; /* previous stack item */
} grp_stack_item;
typedef struct grp_stack_struct {
size_t stack_size; /* number of items on stack */
grp_stack_item* top; /* top item */
} grp_stack;
static char* grp_stack_default = NULL; /* initial value for new instances
of grp_stack_data */
/* the following functions implement the group string stack grp_stack */
static void delete_grp_stack(grp_stack** mystack);
static grp_stack_item* grp_stack_append(
grp_stack_item* last, grp_stack_data data
);
static grp_stack_data grp_stack_remove(grp_stack_item* last);
static grp_stack* new_grp_stack(void);
static grp_stack_data pop_grp_stack(grp_stack* mystack);
static void push_grp_stack(grp_stack* mystack, grp_stack_data data);
static grp_stack_data shift_grp_stack(grp_stack* mystack);
/* static void unshift_grp_stack(grp_stack* mystack, grp_stack_data data); */
int fits_clean_url(char *inURL, /* I input URL string */
char *outURL, /* O output URL string */
int *status)
/*
clean the URL by eliminating any ".." or "." specifiers in the inURL
string, and write the output to the outURL string.
Note that this function must have a valid Unix-style URL as input; platform
dependent path strings are not allowed.
*/
{
grp_stack* mystack; /* stack to hold pieces of URL */
char* tmp;
if(*status) return *status;
mystack = new_grp_stack();
*outURL = 0;
do {
/* handle URL scheme and domain if they exist */
tmp = strstr(inURL, "://");
if(tmp) {
/* there is a URL scheme, so look for the end of the domain too */
tmp = strchr(tmp + 3, '/');
if(tmp) {
/* tmp is now the end of the domain, so
* copy URL scheme and domain as is, and terminate by hand */
size_t string_size = (size_t) (tmp - inURL);
strncpy(outURL, inURL, string_size);
outURL[string_size] = 0;
/* now advance the input pointer to just after the domain and go on */
inURL = tmp;
} else {
/* '/' was not found, which means there are no path-like
* portions, so copy whole inURL to outURL and we're done */
strcpy(outURL, inURL);
continue; /* while(0) */
}
}
/* explicitly copy a leading / (absolute path) */
if('/' == *inURL) strcat(outURL, "/");
/* now clean the remainder of the inURL. push URL segments onto
* stack, dealing with .. and . as we go */
tmp = strtok(inURL, "/"); /* finds first / */
while(tmp) {
if(!strcmp(tmp, "..")) {
/* discard previous URL segment, if there was one. if not,
* add the .. to the stack if this is *not* an absolute path
* (for absolute paths, leading .. has no effect, so skip it) */
if(0 < mystack->stack_size) pop_grp_stack(mystack);
else if('/' != *inURL) push_grp_stack(mystack, tmp);
} else {
/* always just skip ., but otherwise add segment to stack */
if(strcmp(tmp, ".")) push_grp_stack(mystack, tmp);
}
tmp = strtok(NULL, "/"); /* get the next segment */
}
/* stack now has pieces of cleaned URL, so just catenate them
* onto output string until stack is empty */
while(0 < mystack->stack_size) {
tmp = shift_grp_stack(mystack);
strcat(outURL, tmp);
strcat(outURL, "/");
}
outURL[strlen(outURL) - 1] = 0; /* blank out trailing / */
} while(0);
delete_grp_stack(&mystack);
return *status;
}
/* free all stack contents using pop_grp_stack before freeing the
* grp_stack itself */
static void delete_grp_stack(grp_stack** mystack) {
if(!mystack || !*mystack) return;
while((*mystack)->stack_size) pop_grp_stack(*mystack);
free(*mystack);
*mystack = NULL;
}
/* append an item to the stack, handling the special case of the first
* item appended */
static grp_stack_item* grp_stack_append(
grp_stack_item* last, grp_stack_data data
) {
/* first create a new stack item, and copy data to it */
grp_stack_item* new_item = (grp_stack_item*) malloc(sizeof(grp_stack_item));
new_item->data = data;
if(last) {
/* attach this item between the "last" item and its "next" item */
new_item->next = last->next;
new_item->prev = last;
last->next->prev = new_item;
last->next = new_item;
} else {
/* stack is empty, so "next" and "previous" both point back to it */
new_item->next = new_item;
new_item->prev = new_item;
}
return new_item;
}
/* remove an item from the stack, handling the special case of the last
* item removed */
static grp_stack_data grp_stack_remove(grp_stack_item* last) {
grp_stack_data retval = last->data;
last->prev->next = last->next;
last->next->prev = last->prev;
free(last);
return retval;
}
/* create new stack dynamically, and give it valid initial values */
static grp_stack* new_grp_stack(void) {
grp_stack* retval = (grp_stack*) malloc(sizeof(grp_stack));
if(retval) {
retval->stack_size = 0;
retval->top = NULL;
}
return retval;
}
/* return the value at the top of the stack and remove it, updating
* stack_size. top->prev becomes the new "top" */
static grp_stack_data pop_grp_stack(grp_stack* mystack) {
grp_stack_data retval = grp_stack_default;
if(mystack && mystack->top) {
grp_stack_item* newtop = mystack->top->prev;
retval = grp_stack_remove(mystack->top);
mystack->top = newtop;
if(0 == --mystack->stack_size) mystack->top = NULL;
}
return retval;
}
/* add to the stack after the top element. the added element becomes
* the new "top" */
static void push_grp_stack(grp_stack* mystack, grp_stack_data data) {
if(!mystack) return;
mystack->top = grp_stack_append(mystack->top, data);
++mystack->stack_size;
return;
}
/* return the value at the bottom of the stack and remove it, updating
* stack_size. "top" pointer is unaffected */
static grp_stack_data shift_grp_stack(grp_stack* mystack) {
grp_stack_data retval = grp_stack_default;
if(mystack && mystack->top) {
retval = grp_stack_remove(mystack->top->next); /* top->next == bottom */
if(0 == --mystack->stack_size) mystack->top = NULL;
}
return retval;
}
/* add to the stack after the top element. "top" is unaffected, except
* in the special case of an initially empty stack */
/* static void unshift_grp_stack(grp_stack* mystack, grp_stack_data data) {
if(!mystack) return;
if(mystack->top) grp_stack_append(mystack->top, data);
else mystack->top = grp_stack_append(NULL, data);
++mystack->stack_size;
return;
} */
/*--------------------------------------------------------------------------*/
int fits_url2relurl(char *refURL, /* I reference URL string */
char *absURL, /* I absoulute URL string to process */
char *relURL, /* O resulting relative URL string */
int *status)
/*
create a relative URL to the file referenced by absURL with respect to the
reference URL refURL. The relative URL is returned in relURL.
Both refURL and absURL must be absolute URL strings; i.e. either begin
with an access method specification "XXX://" or with a '/' character
signifiying that they are absolute file paths.
Note that it is possible to make a relative URL from two input URLs
(absURL and refURL) that are not compatable. This function does not
check to see if the resulting relative URL makes any sence. For instance,
it is impossible to make a relative URL from the following two inputs:
absURL = ftp://a.b.c.com/x/y/z/foo.fits
refURL = /a/b/c/ttt.fits
The resulting relURL will be:
../../../ftp://a.b.c.com/x/y/z/foo.fits
Which is syntically correct but meaningless. The problem is that a file
with an access method of ftp:// cannot be expressed a a relative URL to
a local disk file.
*/
{
int i,j;
int refcount,abscount;
int refsize,abssize;
int done;
if(*status != 0) return(*status);
/* initialize the relative URL string */
relURL[0] = 0;
do
{
/*
refURL and absURL must be absolute to process
*/
if(!(fits_is_url_absolute(refURL) || *refURL == '/') ||
!(fits_is_url_absolute(absURL) || *absURL == '/'))
{
*status = URL_PARSE_ERROR;
ffpmsg("Cannot make rel. URL from non abs. URLs (fits_url2relurl)");
continue;
}
/* determine the size of the refURL and absURL strings */
refsize = strlen(refURL);
abssize = strlen(absURL);
/* process the two URL strings and build the relative URL between them */
for(done = 0, refcount = 0, abscount = 0;
!done && refcount < refsize && abscount < abssize;
++refcount, ++abscount)
{
for(; abscount < abssize && absURL[abscount] == '/'; ++abscount);
for(; refcount < refsize && refURL[refcount] == '/'; ++refcount);
/* find the next path segment in absURL */
for(i = abscount; absURL[i] != '/' && i < abssize; ++i);
/* find the next path segment in refURL */
for(j = refcount; refURL[j] != '/' && j < refsize; ++j);
/* do the two path segments match? */
if(i == j &&
strncmp(absURL+abscount, refURL+refcount,i-refcount) == 0)
{
/* they match, so ignore them and continue */
abscount = i; refcount = j;
continue;
}
/* We found a difference in the paths in refURL and absURL.
For every path segment remaining in the refURL string, append
a "../" path segment to the relataive URL relURL.
*/
for(j = refcount; j < refsize; ++j)
if(refURL[j] == '/') strcat(relURL,"../");
/* copy all remaining characters of absURL to the output relURL */
strcat(relURL,absURL+abscount);
/* we are done building the relative URL */
done = 1;
}
}while(0);
return(*status);
}
/*--------------------------------------------------------------------------*/
int fits_relurl2url(char *refURL, /* I reference URL string */
char *relURL, /* I relative URL string to process */
char *absURL, /* O absolute URL string */
int *status)
/*
create an absolute URL from a relative url and a reference URL. The
reference URL is given by the FITS file pointed to by fptr.
The construction of the absolute URL from the partial and reference URl
is performed using the rules set forth in:
http://www.w3.org/Addressing/URL/URL_TOC.html
and
http://www.w3.org/Addressing/URL/4_3_Partial.html
Note that the relative URL string relURL must conform to the Unix-like
URL syntax; host dependent partial URL strings are not allowed.
*/
{
int i;
char tmpStr[FLEN_FILENAME];
char *tmpStr1, *tmpStr2;
if(*status != 0) return(*status);
do
{
/*
make a copy of the reference URL string refURL for parsing purposes
*/
strcpy(tmpStr,refURL);
/*
if the reference file has an access method of mem:// or shmem://
then we cannot use it as the basis of an absolute URL construction
for a partial URL
*/
if(strncasecmp(tmpStr,"MEM:",4) == 0 ||
strncasecmp(tmpStr,"SHMEM:",6) == 0)
{
ffpmsg("ref URL has access mem:// or shmem:// (fits_relurl2url)");
ffpmsg(" cannot construct full URL from a partial URL and ");
ffpmsg(" MEM/SHMEM base URL");
*status = URL_PARSE_ERROR;
continue;
}
if(relURL[0] != '/')
{
/*
just append the relative URL string to the reference URL
string (minus the reference URL file name) to form the
absolute URL string
*/
tmpStr1 = strrchr(tmpStr,'/');
if(tmpStr1 != NULL) tmpStr1[1] = 0;
else tmpStr[0] = 0;
strcat(tmpStr,relURL);
}
else
{
/*
have to parse the refURL string for the first occurnace of the
same number of '/' characters as contained in the beginning of
location that is not followed by a greater number of consective
'/' charaters (yes, that is a confusing statement); this is the
location in the refURL string where the relURL string is to
be appended to form the new absolute URL string
*/
/*
first, build up a slash pattern string that has one more
slash in it than the starting slash pattern of the
relURL string
*/
strcpy(absURL,"/");
for(i = 0; relURL[i] == '/'; ++i) strcat(absURL,"/");
/*
loop over the refURL string until the slash pattern stored
in absURL is no longer found
*/
for(tmpStr1 = tmpStr, i = strlen(absURL);
(tmpStr2 = strstr(tmpStr1,absURL)) != NULL;
tmpStr1 = tmpStr2 + i);
/* reduce the slash pattern string by one slash */
absURL[i-1] = 0;
/*
search for the slash pattern in the remaining portion
of the refURL string
*/
tmpStr2 = strstr(tmpStr1,absURL);
/* if no slash pattern match was found */
if(tmpStr2 == NULL)
{
/* just strip off the file name from the refURL */
tmpStr2 = strrchr(tmpStr1,'/');
if(tmpStr2 != NULL) tmpStr2[0] = 0;
else tmpStr[0] = 0;
}
else
{
/* set a string terminator at the slash pattern match */
*tmpStr2 = 0;
}
/*
conatenate the relURL string to the refURL string to form
the absURL
*/
strcat(tmpStr,relURL);
}
/*
normalize the absURL by removing any ".." or "." specifiers
in the string
*/
*status = fits_clean_url(tmpStr,absURL,status);
}while(0);
return(*status);
}
/*--------------------------------------------------------------------------*/
int fits_encode_url(char *inpath, /* I URL to be encoded */
char *outpath, /* O output encoded URL */
int *status)
/*
encode all URL "unsafe" and "reserved" characters using the "%XX"
convention, where XX stand for the two hexidecimal digits of the
encode character's ASCII code.
Note that the output path is at least as large as, if not larger than
the input path, so that OUTPATH should be passed to this function
with room for growth. If not a runtime error could result. It is
assumed that OUTPATH has been allocated with enough room to hold
the resulting encoded URL.
This function was adopted from code in the libwww.a library available
via the W3 consortium <URL: http://www.w3.org>
*/
{
unsigned char a;
char *p;
char *q;
char *hex = "0123456789ABCDEF";
unsigned const char isAcceptable[96] =
{/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xA 0xB 0xC 0xD 0xE 0xF */
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xF,0xE,0x0,0xF,0xF,0xC,
/* 2x !"#$%&'()*+,-./ */
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0x8,0x0,0x0,0x0,0x0,0x0,
/* 3x 0123456789:;<=>? */
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
/* 4x @ABCDEFGHIJKLMNO */
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0x0,0x0,0x0,0x0,0xF,
/* 5X PQRSTUVWXYZ[\]^_ */
0x0,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
/* 6x `abcdefghijklmno */
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0x0,0x0,0x0,0x0,0x0
/* 7X pqrstuvwxyz{\}~DEL */
};
if(*status != 0) return(*status);
/* loop over all characters in inpath until '\0' is encountered */
for(q = outpath, p = inpath; *p; p++)
{
a = (unsigned char)*p;
/* if the charcter requires encoding then process it */
if(!( a>=32 && a<128 && (isAcceptable[a-32])))
{
/* add a '%' character to the outpath */
*q++ = HEX_ESCAPE;
/* add the most significant ASCII code hex value */
*q++ = hex[a >> 4];
/* add the least significant ASCII code hex value */
*q++ = hex[a & 15];
}
/* else just copy the character as is */
else *q++ = *p;
}
/* null terminate the outpath string */
*q++ = 0;
return(*status);
}
/*---------------------------------------------------------------------------*/
int fits_unencode_url(char *inpath, /* I input URL with encoding */
char *outpath, /* O unencoded URL */
int *status)
/*
unencode all URL "unsafe" and "reserved" characters to their actual
ASCII representation. All tokens of the form "%XX" where XX is the
hexidecimal code for an ASCII character, are searched for and
translated into the actuall ASCII character (so three chars become
1 char).
It is assumed that OUTPATH has enough room to hold the unencoded
URL.
This function was adopted from code in the libwww.a library available
via the W3 consortium <URL: http://www.w3.org>
*/
{
char *p;
char *q;
char c;
if(*status != 0) return(*status);
p = inpath;
q = outpath;
/*
loop over all characters in the inpath looking for the '%' escape
character; if found the process the escape sequence
*/
while(*p != 0)
{
/*
if the character is '%' then unencode the sequence, else
just copy the character from inpath to outpath
*/
if (*p == HEX_ESCAPE)
{
if((c = *(++p)) != 0)
{
*q = (
(c >= '0' && c <= '9') ?
(c - '0') : ((c >= 'A' && c <= 'F') ?
(c - 'A' + 10) : (c - 'a' + 10))
)*16;
if((c = *(++p)) != 0)
{
*q = *q + (
(c >= '0' && c <= '9') ?
(c - '0') : ((c >= 'A' && c <= 'F') ?
(c - 'A' + 10) : (c - 'a' + 10))
);
p++, q++;
}
}
}
else
*q++ = *p++;
}
/* terminate the outpath */
*q = 0;
return(*status);
}
/*---------------------------------------------------------------------------*/
int fits_is_url_absolute(char *url)
/*
Return a True (1) or False (0) value indicating whether or not the passed
URL string contains an access method specifier or not. Note that this is
a boolean function and it neither reads nor returns the standard error
status parameter
*/
{
char *tmpStr1, *tmpStr2;
char reserved[] = {':',';','/','?','@','&','=','+','$',','};
/*
The rule for determing if an URL is relative or absolute is that it (1)
must have a colon ":" and (2) that the colon must appear before any other
reserved URL character in the URL string. We first see if a colon exists,
get its position in the string, and then check to see if any of the other
reserved characters exists and if their position in the string is greater
than that of the colons.
*/
if( (tmpStr1 = strchr(url,reserved[0])) != NULL &&
((tmpStr2 = strchr(url,reserved[1])) == NULL || tmpStr2 > tmpStr1) &&
((tmpStr2 = strchr(url,reserved[2])) == NULL || tmpStr2 > tmpStr1) &&
((tmpStr2 = strchr(url,reserved[3])) == NULL || tmpStr2 > tmpStr1) &&
((tmpStr2 = strchr(url,reserved[4])) == NULL || tmpStr2 > tmpStr1) &&
((tmpStr2 = strchr(url,reserved[5])) == NULL || tmpStr2 > tmpStr1) &&
((tmpStr2 = strchr(url,reserved[6])) == NULL || tmpStr2 > tmpStr1) &&
((tmpStr2 = strchr(url,reserved[7])) == NULL || tmpStr2 > tmpStr1) &&
((tmpStr2 = strchr(url,reserved[8])) == NULL || tmpStr2 > tmpStr1) &&
((tmpStr2 = strchr(url,reserved[9])) == NULL || tmpStr2 > tmpStr1) )
{
return(1);
}
else
{
return(0);
}
}
|