// CS 509 Advanced Programming II
// Fall 2002
// Programming Assignment 1 Example Solution


#include <iostream>
#include <string>
#include <cassert>


// Some useful typedefs.

   typedef std::string            ss_row;
   typedef ss_row *               spreadsheet;
   typedef std::string::size_type str_indx;
   typedef const char * const     cchar_cptr;


// A column c in line l begins at l[c.begin] and ends at l[c.end - 1].

   struct column {
     str_indx begin, end;
     };

   typedef const column * const   ccol_cptr;


// Deja vu, c++ style.

   static void mask_out(ss_row &, const ss_row &);
   static bool readline(std::istream &, ss_row &);
   static spreadsheet read_spreadsheet(std::istream &, unsigned);
   static bool parse_spec(const std::string &, column &, column &);
   static void print_section(
     std::ostream &, const spreadsheet, ccol_cptr, cchar_cptr);
   static bool split(const std::string &, cchar_cptr, ss_row &, ss_row &);


static column *
find_columns(const ss_row &  mask, str_indx start = 0, unsigned count = 0) {

  // Pick off the column starting at start in mask, storing the column
  // information in the count-th element of the array returned.  The end of the
  // array is marked by the column beginning at string::npos.

  column c, * columns;
  
  c.begin = mask.find_first_not_of(" ", start);
  if (c.begin == std::string::npos)
    columns = new column[count + 1];
  else {
    c.end = mask.find_first_of(" ", c.begin);
    if (c.end == std::string::npos)
      c.end = mask.length();

    columns = find_columns(mask, c.end, count + 1);
    }

  columns[count] = c;

  return columns;
  }


static bool
is_int(const std::string & str) {

  // Return true iff str contains an unsigned integer.

  return !str.empty() and 
         (str.find_first_not_of("0123456789") == std::string::npos);
  }


int 
main(int argc, char * argv[]) {

  const spreadsheet ss = read_spreadsheet(std::cin, 0);

  ss_row mask;
  for (unsigned i = 0; ss[i].length() > 0; i++)
    mask_out(mask, ss[i]);

  ccol_cptr columns = find_columns(mask);

  const char * sep = "";
  for (int i = 1; i < argc; i++) {
    std::cout << sep;
    print_section(std::cout, ss, columns, argv[i]);
    sep = "\n";
    }

  delete [] ss;
  delete [] columns;
  }


static void
mask_out(ss_row & mask, const ss_row & line) {

  // If line[i] is not a space character, store a '*' in mask[i]; if line[i] is
  // a space character, mask[i] is unchanged.  If line is longer than mask,
  // mask is padded with space charaters to the right to make it the same size
  // as line.

  const int diff = line.length() - mask.length();
  if (diff > 0)
    mask += ss_row(diff, ' ');

  assert(mask.length() >= line.length());

  for (int i = line.length() - 1; i >= 0; i--)
    if (line[i] != ' ')
      mask[i] = '*';
  }


static bool
parse_range(const std::string & spec, column & pair) {

  // Parse the range specification spec into pair.  Return true if the parse
  // was successful, false otherwise (in which case the contents of pair is
  // unchanged).
  
  std::string left, right;

  if (split(spec, "-", left, right)) {
    if (!(is_int(left) and is_int(right)))
      return false;
    pair.begin = std::atoi(left.c_str());
    pair.end = std::atoi(right.c_str());
    if (pair.begin > pair.end)
      return false;
    }
  else {
    if (!is_int(spec))
      return false;
    pair.begin = std::atoi(spec.c_str());
    pair.end = pair.begin;
    }

  pair.end++;

  return true;
  }


static bool
parse_spec(const std::string & spec, column & rows, column & cols) {

  // Parse the section spec spec into row and column ranges.  Return true if
  // the parse was successful, false otherwise (in which case the contents of
  // rows and cols is undefined).

  std::string left, right;

  if (!split(spec, ",", left, right))
    return false;

  return parse_range(left, rows) and parse_range(right, cols);
  }


static void
print_row(
  std::ostream & os, const ss_row & row, ccol_cptr columns, const column & col) {

  // Write to output stream os the range of columns given in col from the
  // spreadsheet row rw; columns contains the spreadsheet column layout.

  const char * sep = "";

  for (str_indx i = col.begin; i < col.end; i++) {
    const column & c = columns[i];
    const str_indx 
      b = std::min(c.begin, row.size()),
      e = std::min(c.end, row.size());
    assert((b <= e) and (e <= row.size()));
    os << sep << row.substr(b, e - b);
    sep = " ";
    }
  os << "\n";
  }


static void 
print_section(
  std::ostream & os,
  const spreadsheet ss,
  ccol_cptr columns,
  cchar_cptr spec) {

  // Print the section specified by spec from the spreadsheet ss to the output
  // stream os; columns contains the column definitions.

  column rows, cols;
  
  if (!parse_spec(std::string(spec), rows, cols)) {
    os << "Malformed section spec:  " << spec << "\n";
    return;
    }

  str_indx max_row;
  for (max_row = 0; ss[max_row].length() != 0; max_row++) { }
  str_indx max_col;
  for (max_col = 0; columns[max_col].begin != std::string::npos; max_col++) { }

  if ((rows.end > max_row) or (cols.end > max_col)) {
    os << "Section spec out of bounds:  " << spec << "\n";
    return;
    }

  for (str_indx i = rows.begin; i < rows.end; i++)
    print_row(os, ss[i], columns, cols);
  }


static spreadsheet
read_spreadsheet(std::istream & is, unsigned count) {

  // Read the next spreadsheet line from the input stream is and store it as
  // the count-th element of the array returned.  The last array element is the
  // empty string.

  ss_row line;
  spreadsheet ss;

  if (!readline(is, line)) {
    line = "";
    ss = new ss_row[count + 1];
    }
  else
    ss = read_spreadsheet(is, count + 1);

  ss[count] = line;

  return ss;
  }


static bool 
readline(std::istream & is, ss_row & line) {

  // Read the next non-blank line from input stream is into line; return true
  // if a non-blank line was read.

  while (true) {
    if (!std::getline(is, line))
      return false;
    if (line.find_first_not_of(" ") != std::string::npos)
      return true;
    }
  }


static bool
split(
  const std::string & str, cchar_cptr sep, 
  std::string & left, std::string & right) {

  // Split the string str at the leftmost occurance of any of the characters in
  // sep; store the substring to the left of the separator in left, the
  // substring to the right of the separator in right, and return true.  Return
  // false if str doesn't contain any of the characters in sep; in which case
  // the contents of left and right are unchanged.

  unsigned c = str.find_first_of(sep);
  if (c == std::string::npos) 
    return false;

  left = str.substr(0, c);
  c++;
  right = str.substr(c, str.length() - c);

  return true;
  }


// $Log:$


syntax highlighted by Code2HTML, v. 0.9