f7
f7 is a spreadsheet formula execution library
git clone https://git.vogt.world/f7.git
Log | Files | README.md | LICENSE.md
← All files
name: src/main/js/models/nodes/CellQuery.ts
-rw-r--r--
5659
  1import { ValueException } from "../../errors/ValueException";
  2import { AlphaUtils } from "../../utils/AlphaUtils";
  3import { Range } from "../common/Range";
  4import { isNull } from "../../common/utils/Types";
  5
  6/**
  7 * A cell query represents a query of one or more cells over a single sheet. It is composed of
  8 * two-dimensional ranges: rows and columns, both of which can be unbounded at the lower, or
  9 * upper ends of the range.
 10 */
 11export class CellQuery extends Object {
 12  /**
 13   * Range of columns.
 14   */
 15  readonly columns: Range;
 16  /**
 17   * Range of rows.
 18   */
 19  readonly rows: Range;
 20  /**
 21   * Name of the sheet being queried.
 22   */
 23  private sheet: string = null;
 24
 25  constructor(sheet: string, columns: Range, rows: Range) {
 26    super();
 27    this.sheet = sheet;
 28    this.columns = columns;
 29    this.rows = rows;
 30  }
 31
 32  static builder(query?: CellQuery): CellQueryBuilder {
 33    return new CellQueryBuilder(query);
 34  }
 35
 36  getFormattedSheetName(): string {
 37    if (this.sheet && this.sheet.startsWith("'")) {
 38      return this.sheet.substr(1, this.sheet.length - 2);
 39    }
 40    return this.sheet;
 41  }
 42
 43  /**
 44   * Does this query intersect with the other one?
 45   *
 46   * @param other - other query.
 47   * @return true if at least one cell overlaps.
 48   */
 49  intersects(other: CellQuery): boolean {
 50    return other.columns.isConnected(this.columns) && other.rows.isConnected(this.rows);
 51  }
 52
 53  /**
 54   * Bound this query using an upper column and upper row. Will default to the lower of the upper column passed in,
 55   * and the upper column bound existing on the columns range. Same for rows.
 56   *
 57   * @param upperColumn - upper endpoint for columns to default to.
 58   * @param upperRow    - upper endpoint of rows to default to.
 59   * @return bounded query.
 60   */
 61  toBounded(upperColumn: number, upperRow: number): CellQuery {
 62    if (
 63      !Range.closed(0, upperColumn).isConnected(this.columns) &&
 64      !Range.closed(0, upperRow).isConnected(this.rows)
 65    ) {
 66      return this;
 67    }
 68    return CellQuery.builder(this)
 69      .columnsBetween(
 70        this.columns.lowerEndpoint(),
 71        Math.min(
 72          upperColumn,
 73          this.columns.hasUpperBound() ? this.columns.upperEndpoint() : upperColumn
 74        )
 75      )
 76      .rowsBetween(
 77        this.rows.lowerEndpoint(),
 78        Math.min(upperRow, this.rows.hasUpperBound() ? this.rows.upperEndpoint() : upperRow)
 79      )
 80      .build();
 81  }
 82
 83  toString() {
 84    const sheet = isNull(this.sheet) ? "" : `${this.sheet}!`;
 85    const upperColumn = this.columns.hasUpperBound()
 86      ? AlphaUtils.oneIndexedNumberToLetter(this.columns.upper + 1)
 87      : "";
 88    const lowerColumn = this.columns.hasLowerBound()
 89      ? AlphaUtils.oneIndexedNumberToLetter(this.columns.lower + 1)
 90      : "";
 91    const upperRow = this.rows.hasUpperBound() ? (this.rows.upper + 1).toString() : "";
 92    const lowerRow = this.rows.hasLowerBound() ? (this.rows.lower + 1).toString() : "";
 93    return `${sheet}${lowerColumn}${lowerRow}:${upperColumn}${upperRow}`;
 94  }
 95}
 96
 97/**
 98 * Easier way to build a query.
 99 */
100class CellQueryBuilder {
101  private sheet: string = null;
102  private columns: Range;
103  private rows: Range;
104
105  constructor(cellQuery?: CellQuery) {
106    if (cellQuery) {
107      this.sheet = cellQuery.getFormattedSheetName();
108      this.columns = cellQuery.columns;
109      this.rows = cellQuery.rows;
110    }
111  }
112
113  setSheet(sheet: string): CellQueryBuilder {
114    this.sheet = sheet;
115    return this;
116  }
117
118  columnsBetween(low: number, high: number): CellQueryBuilder {
119    this.columns = Range.closed(low, high);
120    return this;
121  }
122
123  columnsBetweenAlpha(low: string, high: string): CellQueryBuilder {
124    this.columns = Range.closed(AlphaUtils.columnToInt(low), AlphaUtils.columnToInt(high));
125    return this;
126  }
127
128  rowsBetween(low: number, high: number): CellQueryBuilder {
129    this.rows = Range.closed(low, high);
130    return this;
131  }
132
133  rowsBetweenAlpha(low: string, high: string): CellQueryBuilder {
134    this.rows = Range.closed(parseInt(low) - 1, parseInt(high) - 1);
135    return this;
136  }
137
138  columnsStartingAt(low: number): CellQueryBuilder {
139    this.columns = Range.atLeast(low);
140    return this;
141  }
142
143  rowsStartingAt(low: number): CellQueryBuilder {
144    this.rows = Range.atLeast(low);
145    return this;
146  }
147
148  openColumnsStartingAtZero(): CellQueryBuilder {
149    this.columns = Range.atLeast(0);
150    return this;
151  }
152
153  openColumnsStartingAt(columnString: string): CellQueryBuilder {
154    this.columns = Range.atLeast(AlphaUtils.columnToInt(columnString));
155    return this;
156  }
157
158  openColumnsStartingAtNumber(column: number): CellQueryBuilder {
159    this.columns = Range.atLeast(column);
160    return this;
161  }
162
163  openRowsStartingAtZero(): CellQueryBuilder {
164    this.rows = Range.atLeast(0);
165    return this;
166  }
167
168  openRowsStartingAtNumber(row: number): CellQueryBuilder {
169    this.rows = Range.atLeast(row);
170    return this;
171  }
172
173  openRowsStartingAt(rowString: string): CellQueryBuilder {
174    this.rows = Range.atLeast(AlphaUtils.rowToInt(rowString));
175    return this;
176  }
177
178  expand(query: CellQuery): CellQueryBuilder {
179    if (this.columns == null) {
180      this.columns = query.columns;
181    } else {
182      this.columns = this.columns.span(query.columns);
183    }
184    if (this.rows == null) {
185      this.rows = query.rows;
186    } else {
187      this.rows = this.rows.span(query.rows);
188    }
189    if (this.sheet !== null && this.sheet !== query.getFormattedSheetName()) {
190      throw new ValueException("Different sheet names.");
191    } else {
192      this.sheet = query.getFormattedSheetName();
193    }
194
195    return this;
196  }
197
198  build(): CellQuery {
199    return new CellQuery(this.sheet, this.columns, this.rows);
200  }
201}