1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.html.serializer;
16
17 import static org.htmlunit.css.CssStyleSheet.BLOCK;
18
19 import java.util.List;
20
21 import org.htmlunit.Page;
22 import org.htmlunit.SgmlPage;
23 import org.htmlunit.WebWindow;
24 import org.htmlunit.css.ComputedCssStyleDeclaration;
25 import org.htmlunit.css.StyleAttributes.Definition;
26 import org.htmlunit.html.DomCDataSection;
27 import org.htmlunit.html.DomComment;
28 import org.htmlunit.html.DomElement;
29 import org.htmlunit.html.DomNode;
30 import org.htmlunit.html.DomText;
31 import org.htmlunit.html.HtmlBody;
32 import org.htmlunit.html.HtmlBreak;
33 import org.htmlunit.html.HtmlCheckBoxInput;
34 import org.htmlunit.html.HtmlDetails;
35 import org.htmlunit.html.HtmlHiddenInput;
36 import org.htmlunit.html.HtmlInlineFrame;
37 import org.htmlunit.html.HtmlInput;
38 import org.htmlunit.html.HtmlMenu;
39 import org.htmlunit.html.HtmlNoFrames;
40 import org.htmlunit.html.HtmlNoScript;
41 import org.htmlunit.html.HtmlOption;
42 import org.htmlunit.html.HtmlOrderedList;
43 import org.htmlunit.html.HtmlPreformattedText;
44 import org.htmlunit.html.HtmlRadioButtonInput;
45 import org.htmlunit.html.HtmlResetInput;
46 import org.htmlunit.html.HtmlScript;
47 import org.htmlunit.html.HtmlSelect;
48 import org.htmlunit.html.HtmlStyle;
49 import org.htmlunit.html.HtmlSubmitInput;
50 import org.htmlunit.html.HtmlSummary;
51 import org.htmlunit.html.HtmlTable;
52 import org.htmlunit.html.HtmlTableCell;
53 import org.htmlunit.html.HtmlTableFooter;
54 import org.htmlunit.html.HtmlTableHeader;
55 import org.htmlunit.html.HtmlTableRow;
56 import org.htmlunit.html.HtmlTextArea;
57 import org.htmlunit.html.HtmlTitle;
58 import org.htmlunit.html.HtmlUnorderedList;
59 import org.htmlunit.html.TableRowGroup;
60 import org.htmlunit.html.serializer.HtmlSerializerVisibleText.HtmlSerializerTextBuilder.Mode;
61 import org.htmlunit.util.StringUtils;
62
63
64
65
66
67
68
69
70
71 public class HtmlSerializerVisibleText {
72
73
74
75
76
77
78 public String asText(final DomNode node) {
79 if (node instanceof HtmlBreak) {
80 return "";
81 }
82 final HtmlSerializerTextBuilder builder = new HtmlSerializerTextBuilder();
83 appendNode(builder, node, whiteSpaceStyle(node, Mode.WHITE_SPACE_NORMAL));
84 return builder.getText();
85 }
86
87
88
89
90
91
92
93
94 protected void appendChildren(final HtmlSerializerTextBuilder builder, final DomNode node, final Mode mode) {
95 for (final DomNode child : node.getChildren()) {
96 appendNode(builder, child, updateWhiteSpaceStyle(node, mode));
97 }
98 }
99
100
101
102
103
104
105
106
107
108 protected void appendNode(final HtmlSerializerTextBuilder builder, final DomNode node, final Mode mode) {
109 if (node instanceof DomCDataSection) {
110
111 }
112 else if (node instanceof DomText text1) {
113 appendText(builder, text1, mode);
114 }
115 else if (node instanceof DomComment comment) {
116 appendComment(builder, comment, mode);
117 }
118 else if (node instanceof HtmlBreak break1) {
119 appendBreak(builder, break1, mode);
120 }
121 else if (node instanceof HtmlHiddenInput input4) {
122 appendHiddenInput(builder, input4, mode);
123 }
124 else if (node instanceof HtmlScript script1) {
125 appendScript(builder, script1, mode);
126 }
127 else if (node instanceof HtmlStyle style) {
128 appendStyle(builder, style, mode);
129 }
130 else if (node instanceof HtmlNoFrames frames) {
131 appendNoFrames(builder, frames, mode);
132 }
133 else if (node instanceof HtmlTextArea area) {
134 appendTextArea(builder, area, mode);
135 }
136 else if (node instanceof HtmlTitle title) {
137 appendTitle(builder, title, mode);
138 }
139 else if (node instanceof HtmlTableRow row) {
140 appendTableRow(builder, row, mode);
141 }
142 else if (node instanceof HtmlSelect select) {
143 appendSelect(builder, select, mode);
144 }
145 else if (node instanceof HtmlOption option) {
146 appendOption(builder, option, mode);
147 }
148 else if (node instanceof HtmlSubmitInput input3) {
149 appendSubmitInput(builder, input3, mode);
150 }
151 else if (node instanceof HtmlResetInput input2) {
152 appendResetInput(builder, input2, mode);
153 }
154 else if (node instanceof HtmlCheckBoxInput input1) {
155 appendCheckBoxInput(builder, input1, mode);
156 }
157 else if (node instanceof HtmlRadioButtonInput input) {
158 appendRadioButtonInput(builder, input, mode);
159 }
160 else if (node instanceof HtmlInput) {
161
162 }
163 else if (node instanceof HtmlTable table) {
164 appendTable(builder, table, mode);
165 }
166 else if (node instanceof HtmlOrderedList list1) {
167 appendOrderedList(builder, list1, mode);
168 }
169 else if (node instanceof HtmlUnorderedList list) {
170 appendUnorderedList(builder, list, mode);
171 }
172 else if (node instanceof HtmlPreformattedText text) {
173 appendPreformattedText(builder, text, mode);
174 }
175 else if (node instanceof HtmlInlineFrame frame) {
176 appendInlineFrame(builder, frame, mode);
177 }
178 else if (node instanceof HtmlMenu menu) {
179 appendMenu(builder, menu, mode);
180 }
181 else if (node instanceof HtmlDetails details) {
182 appendDetails(builder, details, mode);
183 }
184 else if (node instanceof HtmlNoScript script && node.getPage().getWebClient().isJavaScriptEnabled()) {
185 appendNoScript(builder, script, mode);
186 }
187 else {
188 appendDomNode(builder, node, mode);
189 }
190 }
191
192
193
194
195
196
197
198
199 protected void appendDomNode(final HtmlSerializerTextBuilder builder,
200 final DomNode domNode, final Mode mode) {
201 final boolean block;
202 if (domNode instanceof HtmlBody) {
203 block = false;
204 }
205 else if (domNode instanceof DomElement element) {
206 final WebWindow window = domNode.getPage().getEnclosingWindow();
207 final String display = window.getComputedStyle(element, null).getDisplay();
208 block = BLOCK.equals(display);
209 }
210 else {
211 block = false;
212 }
213
214 if (block) {
215 builder.appendBlockSeparator();
216 }
217 appendChildren(builder, domNode, mode);
218 if (block) {
219 builder.appendBlockSeparator();
220 }
221 }
222
223
224
225
226
227
228
229
230 protected void appendHiddenInput(final HtmlSerializerTextBuilder builder,
231 final HtmlHiddenInput htmlHiddenInput, final Mode mode) {
232
233 }
234
235
236
237
238
239
240
241
242 protected void appendScript(final HtmlSerializerTextBuilder builder,
243 final HtmlScript htmlScript, final Mode mode) {
244
245 }
246
247
248
249
250
251
252
253
254 protected void appendStyle(final HtmlSerializerTextBuilder builder,
255 final HtmlStyle htmlStyle, final Mode mode) {
256
257 }
258
259
260
261
262
263
264
265
266 protected void appendNoScript(final HtmlSerializerTextBuilder builder,
267 final HtmlNoScript htmlNoScript, final Mode mode) {
268
269 }
270
271
272
273
274
275
276
277
278 protected void appendNoFrames(final HtmlSerializerTextBuilder builder,
279 final HtmlNoFrames htmlNoFrames, final Mode mode) {
280
281 }
282
283
284
285
286
287
288
289
290 protected void appendSubmitInput(final HtmlSerializerTextBuilder builder,
291 final HtmlSubmitInput htmlSubmitInput, final Mode mode) {
292
293 }
294
295
296
297
298
299
300
301
302 protected void appendInput(final HtmlSerializerTextBuilder builder,
303 final HtmlInput htmlInput, final Mode mode) {
304 builder.append(htmlInput.getValueAttribute(), mode);
305 }
306
307
308
309
310
311
312
313
314 protected void appendResetInput(final HtmlSerializerTextBuilder builder,
315 final HtmlResetInput htmlResetInput, final Mode mode) {
316
317 }
318
319
320
321
322
323
324
325 protected void appendMenu(final HtmlSerializerTextBuilder builder,
326 final HtmlMenu htmlMenu, final Mode mode) {
327 builder.appendBlockSeparator();
328 boolean first = true;
329 for (final DomNode item : htmlMenu.getChildren()) {
330 if (!first) {
331 builder.appendBlockSeparator();
332 }
333 first = false;
334 appendNode(builder, item, mode);
335 }
336 builder.appendBlockSeparator();
337 }
338
339
340
341
342
343
344
345 protected void appendDetails(final HtmlSerializerTextBuilder builder,
346 final HtmlDetails htmlDetails, final Mode mode) {
347 if (htmlDetails.isOpen()) {
348 appendChildren(builder, htmlDetails, mode);
349 return;
350 }
351
352 for (final DomNode child : htmlDetails.getChildren()) {
353 if (child instanceof HtmlSummary) {
354 appendNode(builder, child, mode);
355 }
356 }
357 }
358
359
360
361
362
363
364
365 protected void appendTitle(final HtmlSerializerTextBuilder builder,
366 final HtmlTitle htmlTitle, final Mode mode) {
367
368 }
369
370
371
372
373
374
375
376
377 protected void appendTableRow(final HtmlSerializerTextBuilder builder,
378 final HtmlTableRow htmlTableRow, final Mode mode) {
379 boolean first = true;
380 for (final HtmlTableCell cell : htmlTableRow.getCells()) {
381 if (first) {
382 first = false;
383 }
384 else {
385 builder.appendBlank();
386 }
387 appendChildren(builder, cell, mode);
388 }
389 }
390
391
392
393
394
395
396 protected boolean isDisplayed(final DomNode domNode) {
397 return domNode.isDisplayed();
398 }
399
400
401
402
403
404
405
406
407 protected void appendTextArea(final HtmlSerializerTextBuilder builder,
408 final HtmlTextArea htmlTextArea, final Mode mode) {
409 if (isDisplayed(htmlTextArea)) {
410 builder.append(htmlTextArea.getDefaultValue(), whiteSpaceStyle(htmlTextArea, Mode.PRE));
411 builder.trimRight(Mode.PRE);
412 }
413 }
414
415
416
417
418
419
420
421
422 protected void appendTable(final HtmlSerializerTextBuilder builder,
423 final HtmlTable htmlTable, final Mode mode) {
424 builder.appendBlockSeparator();
425 final String caption = htmlTable.getCaptionText();
426 if (caption != null) {
427 builder.append(caption, mode);
428 builder.appendBlockSeparator();
429 }
430
431 boolean first = true;
432
433
434 final HtmlTableHeader tableHeader = htmlTable.getHeader();
435 if (tableHeader != null) {
436 first = appendTableRows(builder, mode, tableHeader.getRows(), true, null, null);
437 }
438 final HtmlTableFooter tableFooter = htmlTable.getFooter();
439
440 final List<HtmlTableRow> tableRows = htmlTable.getRows();
441 first = appendTableRows(builder, mode, tableRows, first, tableHeader, tableFooter);
442
443 if (tableFooter != null) {
444 first = appendTableRows(builder, mode, tableFooter.getRows(), first, null, null);
445 }
446 else if (tableRows.isEmpty()) {
447 final DomNode firstChild = htmlTable.getFirstChild();
448 if (firstChild != null) {
449 appendNode(builder, firstChild, mode);
450 }
451 }
452
453 builder.appendBlockSeparator();
454 }
455
456
457
458
459
460
461
462
463
464
465
466
467 protected boolean appendTableRows(final HtmlSerializerTextBuilder builder, final Mode mode,
468 final List<HtmlTableRow> rows, boolean first, final TableRowGroup skipParent1,
469 final TableRowGroup skipParent2) {
470 for (final HtmlTableRow row : rows) {
471 if (row.getParentNode() == skipParent1 || row.getParentNode() == skipParent2) {
472 continue;
473 }
474 if (!first) {
475 builder.appendBlockSeparator();
476 }
477 first = false;
478 appendTableRow(builder, row, mode);
479 }
480 return first;
481 }
482
483
484
485
486
487
488
489
490 protected void appendSelect(final HtmlSerializerTextBuilder builder,
491 final HtmlSelect htmlSelect, final Mode mode) {
492 builder.appendBlockSeparator();
493 boolean leadingNlPending = false;
494 final Mode selectMode = whiteSpaceStyle(htmlSelect, mode);
495 for (final DomNode item : htmlSelect.getChildren()) {
496 if (leadingNlPending) {
497 builder.appendBlockSeparator();
498 leadingNlPending = false;
499 }
500
501 builder.resetContentAdded();
502 appendNode(builder, item, whiteSpaceStyle(item, selectMode));
503 if (!leadingNlPending && builder.contentAdded_) {
504 leadingNlPending = true;
505 }
506 }
507 builder.appendBlockSeparator();
508 }
509
510
511
512
513
514
515
516
517 protected void appendOption(final HtmlSerializerTextBuilder builder,
518 final HtmlOption htmlOption, final Mode mode) {
519 appendChildren(builder, htmlOption, mode);
520 }
521
522
523
524
525
526
527
528
529 protected void appendOrderedList(final HtmlSerializerTextBuilder builder,
530 final HtmlOrderedList htmlOrderedList, final Mode mode) {
531 builder.appendBlockSeparator();
532 boolean leadingNlPending = false;
533 final Mode olMode = whiteSpaceStyle(htmlOrderedList, mode);
534 for (final DomNode item : htmlOrderedList.getChildren()) {
535 if (leadingNlPending) {
536 builder.appendBlockSeparator();
537 leadingNlPending = false;
538 }
539
540 builder.resetContentAdded();
541 appendNode(builder, item, whiteSpaceStyle(item, olMode));
542 if (!leadingNlPending && builder.contentAdded_) {
543 leadingNlPending = true;
544 }
545 }
546 builder.appendBlockSeparator();
547 }
548
549
550
551
552
553
554
555 protected void appendUnorderedList(final HtmlSerializerTextBuilder builder,
556 final HtmlUnorderedList htmlUnorderedList, final Mode mode) {
557 builder.appendBlockSeparator();
558 boolean leadingNlPending = false;
559 final Mode ulMode = whiteSpaceStyle(htmlUnorderedList, mode);
560 for (final DomNode item : htmlUnorderedList.getChildren()) {
561 if (leadingNlPending) {
562 builder.appendBlockSeparator();
563 leadingNlPending = false;
564 }
565
566 builder.resetContentAdded();
567 appendNode(builder, item, whiteSpaceStyle(item, ulMode));
568 if (!leadingNlPending && builder.contentAdded_) {
569 leadingNlPending = true;
570 }
571 }
572 builder.appendBlockSeparator();
573 }
574
575
576
577
578
579
580
581
582 protected void appendPreformattedText(final HtmlSerializerTextBuilder builder,
583 final HtmlPreformattedText htmlPreformattedText, final Mode mode) {
584 if (isDisplayed(htmlPreformattedText)) {
585 builder.appendBlockSeparator();
586 appendChildren(builder, htmlPreformattedText, whiteSpaceStyle(htmlPreformattedText, Mode.PRE));
587 builder.appendBlockSeparator();
588 }
589 }
590
591
592
593
594
595
596
597
598 protected void appendInlineFrame(final HtmlSerializerTextBuilder builder,
599 final HtmlInlineFrame htmlInlineFrame, final Mode mode) {
600 if (isDisplayed(htmlInlineFrame)) {
601 builder.appendBlockSeparator();
602 final Page page = htmlInlineFrame.getEnclosedPage();
603 if (page instanceof SgmlPage sgmlPage) {
604 builder.append(sgmlPage.asNormalizedText(), mode);
605 }
606 builder.appendBlockSeparator();
607 }
608 }
609
610
611
612
613
614
615
616
617 protected void appendText(final HtmlSerializerTextBuilder builder, final DomText domText, final Mode mode) {
618 final DomNode parent = domText.getParentNode();
619 if (parent instanceof HtmlTitle
620 || parent instanceof HtmlScript) {
621 builder.append(domText.getData(), Mode.WHITE_SPACE_PRE_LINE);
622 return;
623 }
624
625 if (parent == null
626 || parent instanceof HtmlTitle
627 || parent instanceof HtmlScript
628 || isDisplayed(parent)) {
629 builder.append(domText.getData(), mode);
630 }
631 }
632
633
634
635
636
637
638
639
640 protected void appendComment(final HtmlSerializerTextBuilder builder,
641 final DomComment domComment, final Mode mode) {
642
643 }
644
645
646
647
648
649
650
651
652 protected void appendBreak(final HtmlSerializerTextBuilder builder,
653 final HtmlBreak htmlBreak, final Mode mode) {
654 builder.appendBreak(mode);
655 }
656
657
658
659
660
661
662
663
664 protected void appendCheckBoxInput(final HtmlSerializerTextBuilder builder,
665 final HtmlCheckBoxInput htmlCheckBoxInput, final Mode mode) {
666
667 }
668
669
670
671
672
673
674
675
676 protected void appendRadioButtonInput(final HtmlSerializerTextBuilder builder,
677 final HtmlRadioButtonInput htmlRadioButtonInput, final Mode mode) {
678
679 }
680
681 protected Mode whiteSpaceStyle(final DomNode domNode, final Mode defaultMode) {
682 final Page page = domNode.getPage();
683 if (page != null) {
684 final WebWindow window = page.getEnclosingWindow();
685 if (window.getWebClient().getOptions().isCssEnabled()) {
686 DomNode node = domNode;
687 while (node != null) {
688 if (node instanceof DomElement element) {
689 final ComputedCssStyleDeclaration style = window.getComputedStyle(element, null);
690 final String value = style.getStyleAttribute(Definition.WHITE_SPACE, false);
691 if (!StringUtils.isEmptyOrNull(value)) {
692 if ("normal".equalsIgnoreCase(value)) {
693 return Mode.WHITE_SPACE_NORMAL;
694 }
695 if ("nowrap".equalsIgnoreCase(value)) {
696 return Mode.WHITE_SPACE_NORMAL;
697 }
698 if ("pre".equalsIgnoreCase(value)) {
699 return Mode.WHITE_SPACE_PRE;
700 }
701 if ("pre-wrap".equalsIgnoreCase(value)) {
702 return Mode.WHITE_SPACE_PRE;
703 }
704 if ("pre-line".equalsIgnoreCase(value)) {
705 return Mode.WHITE_SPACE_PRE_LINE;
706 }
707 }
708 }
709 node = node.getParentNode();
710 }
711 }
712 }
713 return defaultMode;
714 }
715
716 protected Mode updateWhiteSpaceStyle(final DomNode domNode, final Mode defaultMode) {
717 final Page page = domNode.getPage();
718 if (page != null) {
719 final WebWindow window = page.getEnclosingWindow();
720 if (window.getWebClient().getOptions().isCssEnabled()) {
721 if (domNode instanceof DomElement element) {
722 final ComputedCssStyleDeclaration style = window.getComputedStyle(element, null);
723 final String value = style.getStyleAttribute(Definition.WHITE_SPACE, false);
724 if (!StringUtils.isEmptyOrNull(value)) {
725 if ("normal".equalsIgnoreCase(value)) {
726 return Mode.WHITE_SPACE_NORMAL;
727 }
728 if ("nowrap".equalsIgnoreCase(value)) {
729 return Mode.WHITE_SPACE_NORMAL;
730 }
731 if ("pre".equalsIgnoreCase(value)) {
732 return Mode.WHITE_SPACE_PRE;
733 }
734 if ("pre-wrap".equalsIgnoreCase(value)) {
735 return Mode.WHITE_SPACE_PRE;
736 }
737 if ("pre-line".equalsIgnoreCase(value)) {
738 return Mode.WHITE_SPACE_PRE_LINE;
739 }
740 }
741 }
742 }
743 }
744 return defaultMode;
745 }
746
747
748
749
750 protected static class HtmlSerializerTextBuilder {
751
752 protected enum Mode {
753
754
755
756 PRE,
757
758
759
760
761
762
763 WHITE_SPACE_NORMAL,
764
765
766
767
768
769 WHITE_SPACE_PRE,
770
771
772
773
774
775
776 WHITE_SPACE_PRE_LINE
777 }
778
779 private enum State {
780 DEFAULT,
781 EMPTY,
782 BLANK_AT_END,
783 BLANK_AT_END_AFTER_NEWLINE,
784 NEWLINE_AT_END,
785 BREAK_AT_END,
786 BLOCK_SEPARATOR_AT_END
787 }
788
789 private State state_;
790 private final StringBuilder builder_;
791 private int trimRightPos_;
792 private boolean contentAdded_;
793
794
795
796
797 public HtmlSerializerTextBuilder() {
798 builder_ = new StringBuilder();
799 state_ = State.EMPTY;
800 trimRightPos_ = 0;
801 }
802
803
804
805
806
807
808
809
810 public void append(final String content, final Mode mode) {
811 if (content == null) {
812 return;
813 }
814 int length = content.length();
815 if (length == 0) {
816 return;
817 }
818
819 length--;
820 final int contentLength = content.length();
821 for (int i = 0; i < contentLength; i++) {
822 char c = content.charAt(i);
823
824
825 if (c == '\r') {
826 if (length != i) {
827 continue;
828 }
829 c = '\n';
830 }
831
832 if (c == '\n') {
833 if (mode == Mode.WHITE_SPACE_PRE) {
834 switch (state_) {
835 case EMPTY:
836 case BLOCK_SEPARATOR_AT_END:
837 break;
838 default:
839 builder_.append('\n');
840 state_ = State.NEWLINE_AT_END;
841 trimRightPos_ = builder_.length();
842 break;
843 }
844 continue;
845 }
846
847 if (mode == Mode.PRE) {
848 builder_.append('\n');
849 state_ = State.NEWLINE_AT_END;
850 trimRightPos_ = builder_.length();
851
852 continue;
853 }
854
855 if (mode == Mode.WHITE_SPACE_PRE_LINE) {
856 switch (state_) {
857 case EMPTY:
858 case BLOCK_SEPARATOR_AT_END:
859 break;
860 default:
861 builder_.append('\n');
862 state_ = State.NEWLINE_AT_END;
863 trimRightPos_ = builder_.length();
864 break;
865 }
866 continue;
867 }
868
869 switch (state_) {
870 case EMPTY:
871 case BLANK_AT_END:
872 case BLANK_AT_END_AFTER_NEWLINE:
873 case BLOCK_SEPARATOR_AT_END:
874 case NEWLINE_AT_END:
875 case BREAK_AT_END:
876 break;
877 default:
878 builder_.append(' ');
879 state_ = State.BLANK_AT_END;
880 break;
881 }
882 continue;
883 }
884
885 if (c == ' ' || c == '\t' || c == '\f') {
886 if (mode == Mode.WHITE_SPACE_PRE || mode == Mode.PRE) {
887 appendBlank();
888 continue;
889 }
890
891 if (mode == Mode.WHITE_SPACE_PRE_LINE) {
892 switch (state_) {
893 case EMPTY:
894 case BLANK_AT_END:
895 case BLANK_AT_END_AFTER_NEWLINE:
896 case BREAK_AT_END:
897 break;
898 default:
899 builder_.append(' ');
900 state_ = State.BLANK_AT_END;
901 break;
902 }
903 continue;
904 }
905
906 switch (state_) {
907 case EMPTY:
908 case BLANK_AT_END:
909 case BLANK_AT_END_AFTER_NEWLINE:
910 case BLOCK_SEPARATOR_AT_END:
911 case NEWLINE_AT_END:
912 case BREAK_AT_END:
913 break;
914 default:
915 builder_.append(' ');
916 state_ = State.BLANK_AT_END;
917 break;
918 }
919 continue;
920 }
921
922 if (c == (char) 160) {
923 appendBlank();
924 if (mode == Mode.WHITE_SPACE_NORMAL || mode == Mode.WHITE_SPACE_PRE_LINE) {
925 state_ = State.DEFAULT;
926 }
927 continue;
928 }
929 builder_.append(c);
930 state_ = State.DEFAULT;
931 trimRightPos_ = builder_.length();
932 contentAdded_ = true;
933 }
934 }
935
936
937
938
939 public void appendBlockSeparator() {
940 switch (state_) {
941 case EMPTY:
942 break;
943 case BLANK_AT_END:
944 builder_.setLength(trimRightPos_);
945 if (builder_.length() == 0) {
946 state_ = State.EMPTY;
947 }
948 else {
949 builder_.append('\n');
950 state_ = State.BLOCK_SEPARATOR_AT_END;
951 }
952 break;
953 case BLANK_AT_END_AFTER_NEWLINE:
954 builder_.setLength(trimRightPos_ - 1);
955 if (builder_.length() == 0) {
956 state_ = State.EMPTY;
957 }
958 else {
959 builder_.append('\n');
960 state_ = State.BLOCK_SEPARATOR_AT_END;
961 }
962 break;
963 case BLOCK_SEPARATOR_AT_END:
964 break;
965 case NEWLINE_AT_END:
966 case BREAK_AT_END:
967 builder_.setLength(builder_.length() - 1);
968 trimRightPos_ = trimRightPos_ - 1;
969 if (builder_.length() == 0) {
970 state_ = State.EMPTY;
971 }
972 else {
973 builder_.append('\n');
974 state_ = State.BLOCK_SEPARATOR_AT_END;
975 }
976 break;
977 default:
978 builder_.append('\n');
979 state_ = State.BLOCK_SEPARATOR_AT_END;
980 break;
981 }
982 }
983
984
985
986
987
988
989 public void appendBreak(final Mode mode) {
990 builder_.setLength(trimRightPos_);
991
992 builder_.append('\n');
993 state_ = State.BREAK_AT_END;
994 trimRightPos_ = builder_.length();
995 }
996
997
998
999
1000 public void appendBlank() {
1001 builder_.append(' ');
1002 state_ = State.BLANK_AT_END;
1003 trimRightPos_ = builder_.length();
1004 }
1005
1006
1007
1008
1009
1010
1011 public void trimRight(final Mode mode) {
1012 if (mode == Mode.PRE) {
1013 switch (state_) {
1014 case BLOCK_SEPARATOR_AT_END:
1015 case NEWLINE_AT_END:
1016 case BREAK_AT_END:
1017 if (trimRightPos_ == builder_.length()) {
1018 trimRightPos_--;
1019 }
1020 break;
1021 default:
1022 break;
1023 }
1024 }
1025
1026 builder_.setLength(trimRightPos_);
1027 state_ = State.DEFAULT;
1028 if (builder_.length() == 0) {
1029 state_ = State.EMPTY;
1030 }
1031 }
1032
1033
1034
1035
1036
1037
1038 public boolean wasContentAdded() {
1039 return contentAdded_;
1040 }
1041
1042
1043
1044
1045 public void resetContentAdded() {
1046 contentAdded_ = false;
1047 }
1048
1049
1050
1051
1052
1053
1054 public String getText() {
1055 return builder_.substring(0, trimRightPos_);
1056 }
1057 }
1058 }