Skip to content

Commit bef0392

Browse files
committed
SpreadsheetViewport.fromUrlFragment
1 parent 1d3d728 commit bef0392

File tree

2 files changed

+350
-1
lines changed

2 files changed

+350
-1
lines changed

src/main/java/walkingkooka/spreadsheet/viewport/SpreadsheetViewportRectangle.java

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,37 @@
1717

1818
package walkingkooka.spreadsheet.viewport;
1919

20+
import walkingkooka.datetime.DateTimeContexts;
21+
import walkingkooka.math.DecimalNumberContexts;
2022
import walkingkooka.net.HasUrlFragment;
2123
import walkingkooka.net.UrlFragment;
24+
import walkingkooka.spreadsheet.formula.SpreadsheetFormulaParsers;
25+
import walkingkooka.spreadsheet.formula.parser.CellSpreadsheetFormulaParserToken;
26+
import walkingkooka.spreadsheet.parser.SpreadsheetParserContext;
27+
import walkingkooka.spreadsheet.parser.SpreadsheetParserContexts;
2228
import walkingkooka.spreadsheet.reference.SpreadsheetCellReference;
2329
import walkingkooka.spreadsheet.reference.SpreadsheetSelection;
30+
import walkingkooka.text.CaseSensitivity;
2431
import walkingkooka.text.CharSequences;
2532
import walkingkooka.text.CharacterConstant;
33+
import walkingkooka.text.cursor.TextCursor;
34+
import walkingkooka.text.cursor.TextCursors;
35+
import walkingkooka.text.cursor.parser.DoubleParserToken;
36+
import walkingkooka.text.cursor.parser.InvalidCharacterExceptionFactory;
37+
import walkingkooka.text.cursor.parser.Parser;
38+
import walkingkooka.text.cursor.parser.ParserReporters;
39+
import walkingkooka.text.cursor.parser.Parsers;
2640
import walkingkooka.text.printer.IndentingPrinter;
2741
import walkingkooka.text.printer.TreePrintable;
42+
import walkingkooka.tree.expression.ExpressionNumberContexts;
43+
import walkingkooka.tree.expression.ExpressionNumberKind;
2844
import walkingkooka.tree.json.JsonNode;
2945
import walkingkooka.tree.json.JsonObject;
3046
import walkingkooka.tree.json.marshall.JsonNodeContext;
3147
import walkingkooka.tree.json.marshall.JsonNodeMarshallContext;
3248
import walkingkooka.tree.json.marshall.JsonNodeUnmarshallContext;
3349

50+
import java.math.MathContext;
3451
import java.util.Objects;
3552

3653
/**
@@ -41,7 +58,9 @@ public final class SpreadsheetViewportRectangle implements Comparable<Spreadshee
4158
TreePrintable,
4259
HasUrlFragment {
4360

44-
final static CharacterConstant SEPARATOR = CharacterConstant.with(':');
61+
final static char SEPARATOR_CHAR = ':';
62+
63+
final static CharacterConstant SEPARATOR = CharacterConstant.with(SEPARATOR_CHAR);
4564

4665
/**
4766
* Parses the width and height parse text in the following format.
@@ -89,6 +108,183 @@ private static double parseDouble(final String token,
89108
}
90109
}
91110

111+
/**
112+
* Parses the width and height parse text in the following format.
113+
* <pre>
114+
* /home/A1/width/200/height/300
115+
* </pre>
116+
* Where width and height are decimal numbers.
117+
*/
118+
public static SpreadsheetViewportRectangle fromUrlFragment(final UrlFragment urlFragment) {
119+
Objects.requireNonNull(urlFragment, "urlFragment");
120+
121+
final String text = urlFragment.value();
122+
final TextCursor cursor = TextCursors.charSequence(text);
123+
124+
SpreadsheetCellReference home = null;
125+
double width = 0;
126+
double height = 0;
127+
128+
final int MODE_SLASH_BEFORE_HOME_TOKEN = 1;
129+
final int MODE_HOME_TOKEN = 2;
130+
final int MODE_SLASH_BEFORE_CELL_REFERENCE = 3;
131+
final int MODE_HOME_TOKEN_CELL_REFERENCE = 4;
132+
133+
final int MODE_SLASH_BEFORE_WIDTH_TOKEN = 5;
134+
final int MODE_WIDTH_TOKEN = 6;
135+
final int MODE_SLASH_BEFORE_WIDTH_VALUE = 7;
136+
final int MODE_WIDTH_VALUE = 8;
137+
138+
final int MODE_SLASH_BEFORE_HEIGHT_TOKEN = 9;
139+
final int MODE_HEIGHT_TOKEN = 10;
140+
final int MODE_SLASH_BEFORE_HEIGHT_VALUE = 11;
141+
final int MODE_HEIGHT_VALUE = 12;
142+
143+
final int MODE_FINISHED = 13;
144+
145+
int mode = MODE_SLASH_BEFORE_HOME_TOKEN;
146+
while (cursor.isNotEmpty()) {
147+
switch (mode) {
148+
case MODE_SLASH_BEFORE_HOME_TOKEN:
149+
SLASH_PARSER.parse(cursor, PARSER_CONTEXT);
150+
mode = MODE_HOME_TOKEN;
151+
break;
152+
case MODE_HOME_TOKEN:
153+
parseTokenOrFail(cursor, HOME_TOKEN_PARSER, HOME_TOKEN);
154+
mode = MODE_SLASH_BEFORE_CELL_REFERENCE;
155+
break;
156+
case MODE_SLASH_BEFORE_CELL_REFERENCE:
157+
SLASH_PARSER.parse(cursor, PARSER_CONTEXT);
158+
mode = MODE_HOME_TOKEN_CELL_REFERENCE;
159+
break;
160+
case MODE_HOME_TOKEN_CELL_REFERENCE:
161+
home = SpreadsheetFormulaParsers.cell()
162+
.parse(
163+
cursor,
164+
PARSER_CONTEXT
165+
).orElseThrow(() -> new IllegalArgumentException("Missing home"))
166+
.cast(CellSpreadsheetFormulaParserToken.class)
167+
.cell();
168+
mode = MODE_SLASH_BEFORE_WIDTH_TOKEN;
169+
break;
170+
171+
case MODE_SLASH_BEFORE_WIDTH_TOKEN:
172+
SLASH_PARSER.parse(cursor, PARSER_CONTEXT);
173+
mode = MODE_WIDTH_TOKEN;
174+
break;
175+
case MODE_WIDTH_TOKEN:
176+
parseTokenOrFail(cursor, WIDTH_TOKEN_PARSER, WIDTH_TOKEN);
177+
mode = MODE_SLASH_BEFORE_WIDTH_VALUE;
178+
break;
179+
case MODE_SLASH_BEFORE_WIDTH_VALUE:
180+
SLASH_PARSER.parse(cursor, PARSER_CONTEXT);
181+
mode = MODE_WIDTH_VALUE;
182+
break;
183+
case MODE_WIDTH_VALUE:
184+
width = parseDoubleOrFail(
185+
cursor,
186+
WIDTH_TOKEN
187+
);
188+
mode = MODE_SLASH_BEFORE_HEIGHT_TOKEN;
189+
break;
190+
191+
case MODE_SLASH_BEFORE_HEIGHT_TOKEN:
192+
SLASH_PARSER.parse(cursor, PARSER_CONTEXT);
193+
mode = MODE_HEIGHT_TOKEN;
194+
break;
195+
case MODE_HEIGHT_TOKEN:
196+
parseTokenOrFail(cursor, HEIGHT_TOKEN_PARSER, HEIGHT_TOKEN);
197+
mode = MODE_SLASH_BEFORE_HEIGHT_VALUE;
198+
break;
199+
case MODE_SLASH_BEFORE_HEIGHT_VALUE:
200+
SLASH_PARSER.parse(cursor, PARSER_CONTEXT);
201+
mode = MODE_HEIGHT_VALUE;
202+
break;
203+
case MODE_HEIGHT_VALUE:
204+
height = parseDoubleOrFail(
205+
cursor,
206+
HEIGHT_TOKEN
207+
);
208+
mode = MODE_FINISHED;
209+
break;
210+
211+
default:
212+
throw new IllegalArgumentException("Invalid mode: " + mode);
213+
}
214+
}
215+
216+
switch (mode) {
217+
case MODE_SLASH_BEFORE_HOME_TOKEN:
218+
case MODE_HOME_TOKEN:
219+
case MODE_SLASH_BEFORE_CELL_REFERENCE:
220+
throw new IllegalArgumentException("Missing home");
221+
case MODE_HOME_TOKEN_CELL_REFERENCE:
222+
case MODE_SLASH_BEFORE_WIDTH_TOKEN:
223+
case MODE_WIDTH_TOKEN:
224+
case MODE_SLASH_BEFORE_WIDTH_VALUE:
225+
throw new IllegalArgumentException("Missing width");
226+
case MODE_WIDTH_VALUE:
227+
case MODE_SLASH_BEFORE_HEIGHT_TOKEN:
228+
case MODE_HEIGHT_TOKEN:
229+
case MODE_SLASH_BEFORE_HEIGHT_VALUE:
230+
case MODE_HEIGHT_VALUE:
231+
throw new IllegalArgumentException("Missing height");
232+
case MODE_FINISHED:
233+
return with(
234+
home,
235+
width,
236+
height
237+
);
238+
default:
239+
throw new IllegalArgumentException("Invalid mode: " + mode);
240+
}
241+
}
242+
243+
private final static Parser<SpreadsheetParserContext> SLASH_PARSER = Parsers.string("/", CaseSensitivity.SENSITIVE)
244+
.orReport(ParserReporters.basic())
245+
.cast();
246+
247+
private final static String HOME_TOKEN = "home";
248+
private final static Parser<SpreadsheetParserContext> HOME_TOKEN_PARSER = Parsers.string(HOME_TOKEN, CaseSensitivity.SENSITIVE);
249+
250+
private final static String WIDTH_TOKEN = "width";
251+
private final static Parser<SpreadsheetParserContext> WIDTH_TOKEN_PARSER = Parsers.string(WIDTH_TOKEN, CaseSensitivity.SENSITIVE);
252+
253+
private final static String HEIGHT_TOKEN = "height";
254+
private final static Parser<SpreadsheetParserContext> HEIGHT_TOKEN_PARSER = Parsers.string(HEIGHT_TOKEN, CaseSensitivity.SENSITIVE);
255+
256+
private static void parseTokenOrFail(final TextCursor cursor,
257+
final Parser<SpreadsheetParserContext> parser,
258+
final String label) {
259+
if(false == parser.parse(cursor, PARSER_CONTEXT).isPresent()) {
260+
throw new IllegalArgumentException("Missing " + label);
261+
}
262+
}
263+
264+
/**
265+
* Used to parse the width or height values within a {@link UrlFragment}.
266+
*/
267+
private static double parseDoubleOrFail(final TextCursor cursor,
268+
final String label) {
269+
return Parsers.doubleParser()
270+
.parse(
271+
cursor,
272+
PARSER_CONTEXT
273+
).orElseThrow(() -> new IllegalArgumentException("Missing " + label))
274+
.cast(DoubleParserToken.class)
275+
.value();
276+
}
277+
278+
private final static SpreadsheetParserContext PARSER_CONTEXT = SpreadsheetParserContexts.basic(
279+
InvalidCharacterExceptionFactory.POSITION_EXPECTED,
280+
DateTimeContexts.fake(),
281+
ExpressionNumberContexts.basic(
282+
ExpressionNumberKind.BIG_DECIMAL,
283+
DecimalNumberContexts.american(MathContext.DECIMAL32)
284+
),
285+
';' // not actually used/
286+
);
287+
92288
/**
93289
* Factory that creates a new {@link SpreadsheetViewportRectangle}.
94290
*/

src/test/java/walkingkooka/spreadsheet/viewport/SpreadsheetViewportRectangleTest.java

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import walkingkooka.HashCodeEqualsDefinedTesting2;
2222
import walkingkooka.ToStringTesting;
2323
import walkingkooka.net.HasUrlFragmentTesting;
24+
import walkingkooka.net.UrlFragment;
2425
import walkingkooka.reflect.ClassTesting2;
2526
import walkingkooka.reflect.JavaVisibility;
2627
import walkingkooka.spreadsheet.reference.SpreadsheetCellReference;
@@ -399,6 +400,158 @@ public RuntimeException parseStringFailedExpected(final RuntimeException cause)
399400
return cause;
400401
}
401402

403+
// parseUrlFragment.................................................................................................
404+
405+
@Test
406+
public void testFromUrlFragmentWithNullFails() {
407+
assertThrows(
408+
NullPointerException.class,
409+
() -> SpreadsheetViewportRectangle.fromUrlFragment(null)
410+
);
411+
}
412+
413+
@Test
414+
public void testFromUrlFragmentWithEmptyFails() {
415+
this.fromUrlFragmentFails(
416+
"",
417+
"Missing home"
418+
);
419+
}
420+
421+
@Test
422+
public void testFromUrlFragmentWithMissingLeadingSlashFails() {
423+
this.fromUrlFragmentFails(
424+
"home/A1",
425+
"Invalid character 'h' at 0 expected \"/\""
426+
);
427+
}
428+
429+
@Test
430+
public void testFromUrlFragmentMissingHomeFails() {
431+
this.fromUrlFragmentFails(
432+
"/missing-home",
433+
"Missing home"
434+
);
435+
}
436+
437+
@Test
438+
public void testFromUrlFragmentWithInvalidHomeFails() {
439+
this.fromUrlFragmentFails(
440+
"/home/!InvalidHome",
441+
"Missing home"
442+
);
443+
}
444+
445+
@Test
446+
public void testFromUrlFragmentWithOnlyHomeFails() {
447+
this.fromUrlFragmentFails(
448+
"/home/A1",
449+
"Missing width"
450+
);
451+
}
452+
453+
@Test
454+
public void testFromUrlFragmentMissingWidthFails() {
455+
this.fromUrlFragmentFails(
456+
"/home/A1/",
457+
"Missing width"
458+
);
459+
}
460+
461+
@Test
462+
public void testFromUrlFragmentMissingWidth2Fails() {
463+
this.fromUrlFragmentFails(
464+
"/home/A1/not-width",
465+
"Missing width"
466+
);
467+
}
468+
469+
@Test
470+
public void testFromUrlFragmentWithInvalidWidthFails() {
471+
this.fromUrlFragmentFails(
472+
"/home/A1/width/!InvalidHome",
473+
"Missing width"
474+
);
475+
}
476+
477+
@Test
478+
public void testFromUrlFragmentWithOnlyHomeAndWidthFails() {
479+
this.fromUrlFragmentFails(
480+
"/home/A1/width/200",
481+
"Missing height"
482+
);
483+
}
484+
485+
@Test
486+
public void testFromUrlFragmentMissingHeightFails() {
487+
this.fromUrlFragmentFails(
488+
"/home/A1/width/200/",
489+
"Missing height"
490+
);
491+
}
492+
493+
@Test
494+
public void testFromUrlFragmentMissingHeight2Fails() {
495+
this.fromUrlFragmentFails(
496+
"/home/A1/width/200/not-height",
497+
"Missing height"
498+
);
499+
}
500+
501+
@Test
502+
public void testFromUrlFragmentWithInvalidHeightFails() {
503+
this.fromUrlFragmentFails(
504+
"/home/A1/width/200/height/!InvalidHome",
505+
"Missing height"
506+
);
507+
}
508+
509+
@Test
510+
public void testFromUrlFragmentWithHomeWidthHeight() {
511+
this.fromUrlFragmentAndCheck(
512+
"/home/A1/width/200/height/300",
513+
"A1:200:300"
514+
);
515+
}
516+
517+
518+
private void fromUrlFragmentFails(final String urlFragment,
519+
final String expected) {
520+
this.fromUrlFragmentFails(
521+
UrlFragment.parse(urlFragment),
522+
new IllegalArgumentException(expected)
523+
);
524+
}
525+
526+
private void fromUrlFragmentFails(final UrlFragment urlFragment,
527+
final IllegalArgumentException expected) {
528+
final IllegalArgumentException thrown = assertThrows(
529+
IllegalArgumentException.class,
530+
() -> SpreadsheetViewportRectangle.fromUrlFragment(urlFragment)
531+
);
532+
533+
this.checkEquals(
534+
expected.getMessage(),
535+
thrown.getMessage()
536+
);
537+
}
538+
539+
private void fromUrlFragmentAndCheck(final String urlFragment,
540+
final String expected) {
541+
this.fromUrlFragmentAndCheck(
542+
UrlFragment.parse(urlFragment),
543+
SpreadsheetViewportRectangle.parse(expected)
544+
);
545+
}
546+
547+
private void fromUrlFragmentAndCheck(final UrlFragment urlFragment,
548+
final SpreadsheetViewportRectangle expected) {
549+
this.checkEquals(
550+
expected,
551+
SpreadsheetViewportRectangle.fromUrlFragment(urlFragment)
552+
);
553+
}
554+
402555
// JsonNodeMarshallingTesting...............................................................................................
403556

404557
@Test

0 commit comments

Comments
 (0)