1+ /*
2+ * fin01.c
3+ *
4+ * Pipeline:
5+ * 1) Download PDF using libcurl
6+ * 2) Convert PDF to text using pdftotext (requires Poppler utils)
7+ * 3) Parse the text output to extract a table of state data
8+ * 4) Offer menu to sort and display
9+ *
10+ * Build:
11+ * gcc -o fin01 fin01.c -lcurl
12+ *
13+ * Requirements:
14+ * - libcurl-dev
15+ * - poppler-utils (for pdftotext)
16+ */
17+
18+ #include <stdio.h>
19+ #include <stdlib.h>
20+ #include <string.h>
21+ #include <curl/curl.h>
22+
23+ typedef struct {
24+ char state [64 ];
25+ int start_salary ;
26+ int avg_salary ;
27+ int min_wage ;
28+ int per_student_spending ;
29+ int he_earn ;
30+ int he_fac_salary ;
31+ } StateData ;
32+
33+ #define MAX_STATES 60
34+ static StateData states [MAX_STATES ];
35+ static int num_states = 0 ;
36+
37+ // libcurl write callback
38+ static size_t write_data (void * ptr , size_t size , size_t nmemb , FILE * stream ) {
39+ return fwrite (ptr , size , nmemb , stream );
40+ }
41+
42+ // Download the PDF
43+ void download_pdf (const char * url , const char * outfile ) {
44+ CURL * curl = curl_easy_init ();
45+ if (!curl ) {
46+ fprintf (stderr , "Failed to init curl\n" );
47+ exit (1 );
48+ }
49+ FILE * fp = fopen (outfile , "wb" );
50+ curl_easy_setopt (curl , CURLOPT_URL , url );
51+ curl_easy_setopt (curl , CURLOPT_WRITEFUNCTION , write_data );
52+ curl_easy_setopt (curl , CURLOPT_WRITEDATA , fp );
53+ CURLcode res = curl_easy_perform (curl );
54+ curl_easy_cleanup (curl );
55+ fclose (fp );
56+ if (res != CURLE_OK ) {
57+ fprintf (stderr , "Error downloading PDF: %s\n" , curl_easy_strerror (res ));
58+ exit (1 );
59+ }
60+ printf ("PDF downloaded to %s\n" , outfile );
61+ }
62+
63+ // Convert PDF to text
64+ void pdf_to_text (const char * pdf , const char * txt ) {
65+ char cmd [512 ];
66+ snprintf (cmd , sizeof (cmd ), "pdftotext -layout %s %s" , pdf , txt );
67+ printf ("Running: %s\n" , cmd );
68+ int rc = system (cmd );
69+ if (rc != 0 ) {
70+ fprintf (stderr , "pdftotext conversion failed\n" );
71+ exit (1 );
72+ }
73+ }
74+
75+ // Parse the text file output
76+ void parse_text (const char * txtfile ) {
77+ FILE * fp = fopen (txtfile , "r" );
78+ if (!fp ) { perror ("fopen" ); exit (1 ); }
79+ char line [256 ];
80+ int in_table = 0 ;
81+ num_states = 0 ;
82+ while (fgets (line , sizeof (line ), fp )) {
83+ // Trim newline
84+ line [strcspn (line , "\r\n" )] = 0 ;
85+ if (!in_table ) {
86+ // detect table header line containing "State" and "Starting"
87+ if (strstr (line , "State" ) && strstr (line , "Starting" )) {
88+ in_table = 1 ;
89+ // skip header underline
90+ fgets (line , sizeof (line ), fp );
91+ }
92+ } else {
93+ if (strlen (line ) < 10 ) break ; // end of table
94+ // Expect fixed-width columns, parse by substrings
95+ char state_name [64 ];
96+ char buf [16 ];
97+ // columns: State [0-20], StartSalary [20-30], AvgSalary [30-40], MinWage [40-50], PerStudent [50-60], HEearn [60-70], HEfac [70-80]
98+ strncpy (state_name , line , 20 ); state_name [20 ] = 0 ;
99+ // cleanup
100+ char * s = state_name ;
101+ while (* s == ' ' ) s ++ ;
102+ strcpy (states [num_states ].state , s );
103+ // parse ints
104+ strncpy (buf , line + 20 , 10 ); buf [10 ]= 0 ; states [num_states ].start_salary = atoi (buf );
105+ strncpy (buf , line + 30 , 10 ); buf [10 ]= 0 ; states [num_states ].avg_salary = atoi (buf );
106+ strncpy (buf , line + 40 , 10 ); buf [10 ]= 0 ; states [num_states ].min_wage = atoi (buf );
107+ strncpy (buf , line + 50 , 10 ); buf [10 ]= 0 ; states [num_states ].per_student_spending = atoi (buf );
108+ strncpy (buf , line + 60 , 10 ); buf [10 ]= 0 ; states [num_states ].he_earn = atoi (buf );
109+ strncpy (buf , line + 70 , 10 ); buf [10 ]= 0 ; states [num_states ].he_fac_salary = atoi (buf );
110+ num_states ++ ;
111+ }
112+ }
113+ fclose (fp );
114+ printf ("Parsed %d states\n" , num_states );
115+ }
116+
117+ // Comparator functions for all 14 menu options
118+ int cmp_start_asc (const void * a , const void * b ) {
119+ return ((StateData * )a )-> start_salary - ((StateData * )b )-> start_salary ;
120+ }
121+ int cmp_start_desc (const void * a , const void * b ) {
122+ return ((StateData * )b )-> start_salary - ((StateData * )a )-> start_salary ;
123+ }
124+ int cmp_avg_asc (const void * a , const void * b ) {
125+ return ((StateData * )a )-> avg_salary - ((StateData * )b )-> avg_salary ;
126+ }
127+ int cmp_avg_desc (const void * a , const void * b ) {
128+ return ((StateData * )b )-> avg_salary - ((StateData * )a )-> avg_salary ;
129+ }
130+ int cmp_start_min_gap_asc (const void * a , const void * b ) {
131+ int gap_a = ((StateData * )a )-> start_salary - ((StateData * )a )-> min_wage ;
132+ int gap_b = ((StateData * )b )-> start_salary - ((StateData * )b )-> min_wage ;
133+ return gap_a - gap_b ;
134+ }
135+ int cmp_start_min_gap_desc (const void * a , const void * b ) {
136+ int gap_a = ((StateData * )a )-> start_salary - ((StateData * )a )-> min_wage ;
137+ int gap_b = ((StateData * )b )-> start_salary - ((StateData * )b )-> min_wage ;
138+ return gap_b - gap_a ;
139+ }
140+ int cmp_avg_min_gap_asc (const void * a , const void * b ) {
141+ int gap_a = ((StateData * )a )-> avg_salary - ((StateData * )a )-> min_wage ;
142+ int gap_b = ((StateData * )b )-> avg_salary - ((StateData * )b )-> min_wage ;
143+ return gap_a - gap_b ;
144+ }
145+ int cmp_avg_min_gap_desc (const void * a , const void * b ) {
146+ int gap_a = ((StateData * )a )-> avg_salary - ((StateData * )a )-> min_wage ;
147+ int gap_b = ((StateData * )b )-> avg_salary - ((StateData * )b )-> min_wage ;
148+ return gap_b - gap_a ;
149+ }
150+ int cmp_per_student_asc (const void * a , const void * b ) {
151+ return ((StateData * )a )-> per_student_spending - ((StateData * )b )-> per_student_spending ;
152+ }
153+ int cmp_per_student_desc (const void * a , const void * b ) {
154+ return ((StateData * )b )-> per_student_spending - ((StateData * )a )-> per_student_spending ;
155+ }
156+ int cmp_start_he_gap_asc (const void * a , const void * b ) {
157+ int gap_a = ((StateData * )a )-> start_salary - ((StateData * )a )-> he_earn ;
158+ int gap_b = ((StateData * )b )-> start_salary - ((StateData * )b )-> he_earn ;
159+ return gap_a - gap_b ;
160+ }
161+ int cmp_start_he_gap_desc (const void * a , const void * b ) {
162+ int gap_a = ((StateData * )a )-> start_salary - ((StateData * )a )-> he_earn ;
163+ int gap_b = ((StateData * )b )-> start_salary - ((StateData * )b )-> he_earn ;
164+ return gap_b - gap_a ;
165+ }
166+ int cmp_avg_hefac_gap_asc (const void * a , const void * b ) {
167+ int gap_a = ((StateData * )a )-> avg_salary - ((StateData * )a )-> he_fac_salary ;
168+ int gap_b = ((StateData * )b )-> avg_salary - ((StateData * )b )-> he_fac_salary ;
169+ return gap_a - gap_b ;
170+ }
171+ int cmp_avg_hefac_gap_desc (const void * a , const void * b ) {
172+ int gap_a = ((StateData * )a )-> avg_salary - ((StateData * )a )-> he_fac_salary ;
173+ int gap_b = ((StateData * )b )-> avg_salary - ((StateData * )b )-> he_fac_salary ;
174+ return gap_b - gap_a ;
175+ }
176+
177+ // Display top results with appropriate data based on sort type
178+ void display_states (int limit , int sort_type ) {
179+ for (int i = 0 ; i < limit && i < num_states ; i ++ ) {
180+ printf ("%2d. %-20s" , i + 1 , states [i ].state );
181+
182+ switch (sort_type ) {
183+ case 1 : case 2 : // Starting salary sorts
184+ printf (" %6d\n" , states [i ].start_salary );
185+ break ;
186+ case 3 : case 4 : // Average salary sorts
187+ printf (" %6d\n" , states [i ].avg_salary );
188+ break ;
189+ case 5 : case 6 : // Starting salary vs minimum wage gap
190+ printf (" %6d\n" , states [i ].start_salary - states [i ].min_wage );
191+ break ;
192+ case 7 : case 8 : // Average salary vs minimum wage gap
193+ printf (" %6d\n" , states [i ].avg_salary - states [i ].min_wage );
194+ break ;
195+ case 9 : case 10 : // Per student spending
196+ printf (" %6d\n" , states [i ].per_student_spending );
197+ break ;
198+ case 11 : case 12 : // Starting salary vs HE ESP earnings gap
199+ printf (" %6d\n" , states [i ].start_salary - states [i ].he_earn );
200+ break ;
201+ case 13 : case 14 : // Average salary vs HE faculty salary gap
202+ printf (" %6d\n" , states [i ].avg_salary - states [i ].he_fac_salary );
203+ break ;
204+ default :
205+ printf (" %6d %6d %6d\n" , states [i ].start_salary , states [i ].avg_salary , states [i ].min_wage );
206+ }
207+ }
208+ }
209+
210+ int main () {
211+ const char * pdf_url = "https://www.nea.org/sites/default/files/2025-04/2025_rankings_and_estimates_report.pdf" ;
212+ download_pdf (pdf_url , "report.pdf" );
213+ pdf_to_text ("report.pdf" , "report.txt" );
214+ parse_text ("report.txt" );
215+
216+ int choice ;
217+ do {
218+ printf ("---\n" );
219+ printf ("1) Sort states in the ascending order based on \"Average Teacher Starting Salary\"\n" );
220+ printf ("2) Sort states in the descending order based on \"Average Teacher Starting Salary\"\n" );
221+ printf ("3) Sort states in the ascending order based on \"Average Teacher Salary\"\n" );
222+ printf ("4) Sort states in the descending order based on \"Average Teacher Salary\"\n" );
223+ printf ("5) Sort states in the ascending order based on gap between \"Average Starting Teacher Salary\" and \"Minimum Living Wage\"\n" );
224+ printf ("6) Sort states in the descending order based on gap between \"Average Starting Teacher Salary\" and \"Minimum Living Wage\"\n" );
225+ printf ("7) Sort states in the ascending order based on gap between \"Average Teacher Salary\" and \"Minimum Living Wage\"\n" );
226+ printf ("8) Sort states in the descending order based on gap between \"Average Teacher Salary\" and \"Minimum Living Wage\"\n" );
227+ printf ("9) Sort states in the ascending order based on \"Per Student Spending\"\n" );
228+ printf ("10) Sort states in the descending order based on \"Per Student Spending\"\n" );
229+ printf ("11) Sort states in the ascending order based on gap between \"Average Starting Teacher Salary\" and \"Average HE ESP Earnings\"\n" );
230+ printf ("12) Sort states in the descending order based on gap between \"Average Starting Teacher Salary\" and \"Average HE ESP Earnings\"\n" );
231+ printf ("13) Sort states in the ascending order based on gap between \"Average Teacher Salary\" and \"Average Higher Ed Faculty Salary\"\n" );
232+ printf ("14) Sort states in the descending order based on gap between \"Average Teacher Salary\" and \"Average Higher Ed Faculty Salary\"\n" );
233+ printf ("15) Exit\n" );
234+ printf ("---\n" );
235+ printf ("Please enter your choice: " );
236+ scanf ("%d" , & choice );
237+ switch (choice ) {
238+ case 1 :
239+ qsort (states , num_states , sizeof (StateData ), cmp_start_asc );
240+ display_states (10 , choice );
241+ break ;
242+ case 2 :
243+ qsort (states , num_states , sizeof (StateData ), cmp_start_desc );
244+ display_states (10 , choice );
245+ break ;
246+ case 3 :
247+ qsort (states , num_states , sizeof (StateData ), cmp_avg_asc );
248+ display_states (10 , choice );
249+ break ;
250+ case 4 :
251+ qsort (states , num_states , sizeof (StateData ), cmp_avg_desc );
252+ display_states (10 , choice );
253+ break ;
254+ case 5 :
255+ qsort (states , num_states , sizeof (StateData ), cmp_start_min_gap_asc );
256+ display_states (10 , choice );
257+ break ;
258+ case 6 :
259+ qsort (states , num_states , sizeof (StateData ), cmp_start_min_gap_desc );
260+ display_states (10 , choice );
261+ break ;
262+ case 7 :
263+ qsort (states , num_states , sizeof (StateData ), cmp_avg_min_gap_asc );
264+ display_states (10 , choice );
265+ break ;
266+ case 8 :
267+ qsort (states , num_states , sizeof (StateData ), cmp_avg_min_gap_desc );
268+ display_states (10 , choice );
269+ break ;
270+ case 9 :
271+ qsort (states , num_states , sizeof (StateData ), cmp_per_student_asc );
272+ display_states (10 , choice );
273+ break ;
274+ case 10 :
275+ qsort (states , num_states , sizeof (StateData ), cmp_per_student_desc );
276+ display_states (10 , choice );
277+ break ;
278+ case 11 :
279+ qsort (states , num_states , sizeof (StateData ), cmp_start_he_gap_asc );
280+ display_states (10 , choice );
281+ break ;
282+ case 12 :
283+ qsort (states , num_states , sizeof (StateData ), cmp_start_he_gap_desc );
284+ display_states (10 , choice );
285+ break ;
286+ case 13 :
287+ qsort (states , num_states , sizeof (StateData ), cmp_avg_hefac_gap_asc );
288+ display_states (10 , choice );
289+ break ;
290+ case 14 :
291+ qsort (states , num_states , sizeof (StateData ), cmp_avg_hefac_gap_desc );
292+ display_states (10 , choice );
293+ break ;
294+ case 15 :
295+ printf ("Goodbye!\n" );
296+ break ;
297+ default :
298+ printf ("Not implemented yet.\n" );
299+ }
300+ } while (choice != 15 );
301+
302+ return 0 ;
303+ }
0 commit comments