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) {
113 appendText(builder, (DomText) node, mode);
114 }
115 else if (node instanceof DomComment) {
116 appendComment(builder, (DomComment) node, mode);
117 }
118 else if (node instanceof HtmlBreak) {
119 appendBreak(builder, (HtmlBreak) node, mode);
120 }
121 else if (node instanceof HtmlHiddenInput) {
122 appendHiddenInput(builder, (HtmlHiddenInput) node, mode);
123 }
124 else if (node instanceof HtmlScript) {
125 appendScript(builder, (HtmlScript) node, mode);
126 }
127 else if (node instanceof HtmlStyle) {
128 appendStyle(builder, (HtmlStyle) node, mode);
129 }
130 else if (node instanceof HtmlNoFrames) {
131 appendNoFrames(builder, (HtmlNoFrames) node, mode);
132 }
133 else if (node instanceof HtmlTextArea) {
134 appendTextArea(builder, (HtmlTextArea) node, mode);
135 }
136 else if (node instanceof HtmlTitle) {
137 appendTitle(builder, (HtmlTitle) node, mode);
138 }
139 else if (node instanceof HtmlTableRow) {
140 appendTableRow(builder, (HtmlTableRow) node, mode);
141 }
142 else if (node instanceof HtmlSelect) {
143 appendSelect(builder, (HtmlSelect) node, mode);
144 }
145 else if (node instanceof HtmlOption) {
146 appendOption(builder, (HtmlOption) node, mode);
147 }
148 else if (node instanceof HtmlSubmitInput) {
149 appendSubmitInput(builder, (HtmlSubmitInput) node, mode);
150 }
151 else if (node instanceof HtmlResetInput) {
152 appendResetInput(builder, (HtmlResetInput) node, mode);
153 }
154 else if (node instanceof HtmlCheckBoxInput) {
155 appendCheckBoxInput(builder, (HtmlCheckBoxInput) node, mode);
156 }
157 else if (node instanceof HtmlRadioButtonInput) {
158 appendRadioButtonInput(builder, (HtmlRadioButtonInput) node, mode);
159 }
160 else if (node instanceof HtmlInput) {
161
162 }
163 else if (node instanceof HtmlTable) {
164 appendTable(builder, (HtmlTable) node, mode);
165 }
166 else if (node instanceof HtmlOrderedList) {
167 appendOrderedList(builder, (HtmlOrderedList) node, mode);
168 }
169 else if (node instanceof HtmlUnorderedList) {
170 appendUnorderedList(builder, (HtmlUnorderedList) node, mode);
171 }
172 else if (node instanceof HtmlPreformattedText) {
173 appendPreformattedText(builder, (HtmlPreformattedText) node, mode);
174 }
175 else if (node instanceof HtmlInlineFrame) {
176 appendInlineFrame(builder, (HtmlInlineFrame) node, mode);
177 }
178 else if (node instanceof HtmlMenu) {
179 appendMenu(builder, (HtmlMenu) node, mode);
180 }
181 else if (node instanceof HtmlDetails) {
182 appendDetails(builder, (HtmlDetails) node, mode);
183 }
184 else if (node instanceof HtmlNoScript && node.getPage().getWebClient().isJavaScriptEnabled()) {
185 appendNoScript(builder, (HtmlNoScript) node, 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) {
206 final WebWindow window = domNode.getPage().getEnclosingWindow();
207 final String display = window.getComputedStyle((DomElement) domNode, 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 builder.appendBlank();
383 }
384 else {
385 first = false;
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) {
604 builder.append(((SgmlPage) page).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 }
623
624 if (parent == null
625 || parent instanceof HtmlTitle
626 || parent instanceof HtmlScript
627 || isDisplayed(parent)) {
628 builder.append(domText.getData(), mode);
629 }
630 }
631
632
633
634
635
636
637
638
639 protected void appendComment(final HtmlSerializerTextBuilder builder,
640 final DomComment domComment, final Mode mode) {
641
642 }
643
644
645
646
647
648
649
650
651 protected void appendBreak(final HtmlSerializerTextBuilder builder,
652 final HtmlBreak htmlBreak, final Mode mode) {
653 builder.appendBreak(mode);
654 }
655
656
657
658
659
660
661
662
663 protected void appendCheckBoxInput(final HtmlSerializerTextBuilder builder,
664 final HtmlCheckBoxInput htmlCheckBoxInput, final Mode mode) {
665
666 }
667
668
669
670
671
672
673
674
675 protected void appendRadioButtonInput(final HtmlSerializerTextBuilder builder,
676 final HtmlRadioButtonInput htmlRadioButtonInput, final Mode mode) {
677
678 }
679
680 protected Mode whiteSpaceStyle(final DomNode domNode, final Mode defaultMode) {
681 final Page page = domNode.getPage();
682 if (page != null) {
683 final WebWindow window = page.getEnclosingWindow();
684 if (window.getWebClient().getOptions().isCssEnabled()) {
685 DomNode node = domNode;
686 while (node != null) {
687 if (node instanceof DomElement) {
688 final ComputedCssStyleDeclaration style = window.getComputedStyle((DomElement) node, null);
689 final String value = style.getStyleAttribute(Definition.WHITE_SPACE, false);
690 if (!StringUtils.isEmptyOrNull(value)) {
691 if ("normal".equalsIgnoreCase(value)) {
692 return Mode.WHITE_SPACE_NORMAL;
693 }
694 if ("nowrap".equalsIgnoreCase(value)) {
695 return Mode.WHITE_SPACE_NORMAL;
696 }
697 if ("pre".equalsIgnoreCase(value)) {
698 return Mode.WHITE_SPACE_PRE;
699 }
700 if ("pre-wrap".equalsIgnoreCase(value)) {
701 return Mode.WHITE_SPACE_PRE;
702 }
703 if ("pre-line".equalsIgnoreCase(value)) {
704 return Mode.WHITE_SPACE_PRE_LINE;
705 }
706 }
707 }
708 node = node.getParentNode();
709 }
710 }
711 }
712 return defaultMode;
713 }
714
715 protected Mode updateWhiteSpaceStyle(final DomNode domNode, final Mode defaultMode) {
716 final Page page = domNode.getPage();
717 if (page != null) {
718 final WebWindow window = page.getEnclosingWindow();
719 if (window.getWebClient().getOptions().isCssEnabled()) {
720 if (domNode instanceof DomElement) {
721 final ComputedCssStyleDeclaration style = window.getComputedStyle((DomElement) domNode, null);
722 final String value = style.getStyleAttribute(Definition.WHITE_SPACE, false);
723 if (!StringUtils.isEmptyOrNull(value)) {
724 if ("normal".equalsIgnoreCase(value)) {
725 return Mode.WHITE_SPACE_NORMAL;
726 }
727 if ("nowrap".equalsIgnoreCase(value)) {
728 return Mode.WHITE_SPACE_NORMAL;
729 }
730 if ("pre".equalsIgnoreCase(value)) {
731 return Mode.WHITE_SPACE_PRE;
732 }
733 if ("pre-wrap".equalsIgnoreCase(value)) {
734 return Mode.WHITE_SPACE_PRE;
735 }
736 if ("pre-line".equalsIgnoreCase(value)) {
737 return Mode.WHITE_SPACE_PRE_LINE;
738 }
739 }
740 }
741 }
742 }
743 return defaultMode;
744 }
745
746
747
748
749 protected static class HtmlSerializerTextBuilder {
750
751 protected enum Mode {
752
753
754
755 PRE,
756
757
758
759
760
761
762 WHITE_SPACE_NORMAL,
763
764
765
766
767
768 WHITE_SPACE_PRE,
769
770
771
772
773
774
775 WHITE_SPACE_PRE_LINE
776 }
777
778 private enum State {
779 DEFAULT,
780 EMPTY,
781 BLANK_AT_END,
782 BLANK_AT_END_AFTER_NEWLINE,
783 NEWLINE_AT_END,
784 BREAK_AT_END,
785 BLOCK_SEPARATOR_AT_END
786 }
787
788 private State state_;
789 private final StringBuilder builder_;
790 private int trimRightPos_;
791 private boolean contentAdded_;
792
793
794
795
796 public HtmlSerializerTextBuilder() {
797 builder_ = new StringBuilder();
798 state_ = State.EMPTY;
799 trimRightPos_ = 0;
800 }
801
802
803
804
805
806
807
808
809 public void append(final String content, final Mode mode) {
810 if (content == null) {
811 return;
812 }
813 int length = content.length();
814 if (length == 0) {
815 return;
816 }
817
818 length--;
819 final int contentLenght = content.length();
820 for (int i = 0; i < contentLenght; i++) {
821 char c = content.charAt(i);
822
823
824 if (c == '\r') {
825 if (length != i) {
826 continue;
827 }
828 c = '\n';
829 }
830
831 if (c == '\n') {
832 if (mode == Mode.WHITE_SPACE_PRE) {
833 switch (state_) {
834 case EMPTY:
835 case BLOCK_SEPARATOR_AT_END:
836 break;
837 default:
838 builder_.append('\n');
839 state_ = State.NEWLINE_AT_END;
840 trimRightPos_ = builder_.length();
841 break;
842 }
843 continue;
844 }
845
846 if (mode == Mode.PRE) {
847 builder_.append('\n');
848 state_ = State.NEWLINE_AT_END;
849 trimRightPos_ = builder_.length();
850
851 continue;
852 }
853
854 if (mode == Mode.WHITE_SPACE_PRE_LINE) {
855 switch (state_) {
856 case EMPTY:
857 case BLOCK_SEPARATOR_AT_END:
858 break;
859 default:
860 builder_.append('\n');
861 state_ = State.NEWLINE_AT_END;
862 trimRightPos_ = builder_.length();
863 break;
864 }
865 continue;
866 }
867
868 switch (state_) {
869 case EMPTY:
870 case BLANK_AT_END:
871 case BLANK_AT_END_AFTER_NEWLINE:
872 case BLOCK_SEPARATOR_AT_END:
873 case NEWLINE_AT_END:
874 case BREAK_AT_END:
875 break;
876 default:
877 builder_.append(' ');
878 state_ = State.BLANK_AT_END;
879 break;
880 }
881 continue;
882 }
883
884 if (c == ' ' || c == '\t' || c == '\f') {
885 if (mode == Mode.WHITE_SPACE_PRE || mode == Mode.PRE) {
886 appendBlank();
887 continue;
888 }
889
890 if (mode == Mode.WHITE_SPACE_PRE_LINE) {
891 switch (state_) {
892 case EMPTY:
893 case BLANK_AT_END:
894 case BLANK_AT_END_AFTER_NEWLINE:
895 case BREAK_AT_END:
896 break;
897 default:
898 builder_.append(' ');
899 state_ = State.BLANK_AT_END;
900 break;
901 }
902 continue;
903 }
904
905 switch (state_) {
906 case EMPTY:
907 case BLANK_AT_END:
908 case BLANK_AT_END_AFTER_NEWLINE:
909 case BLOCK_SEPARATOR_AT_END:
910 case NEWLINE_AT_END:
911 case BREAK_AT_END:
912 break;
913 default:
914 builder_.append(' ');
915 state_ = State.BLANK_AT_END;
916 break;
917 }
918 continue;
919 }
920
921 if (c == (char) 160) {
922 appendBlank();
923 if (mode == Mode.WHITE_SPACE_NORMAL || mode == Mode.WHITE_SPACE_PRE_LINE) {
924 state_ = State.DEFAULT;
925 }
926 continue;
927 }
928 builder_.append(c);
929 state_ = State.DEFAULT;
930 trimRightPos_ = builder_.length();
931 contentAdded_ = true;
932 }
933 }
934
935
936
937
938 public void appendBlockSeparator() {
939 switch (state_) {
940 case EMPTY:
941 break;
942 case BLANK_AT_END:
943 builder_.setLength(trimRightPos_);
944 if (builder_.length() == 0) {
945 state_ = State.EMPTY;
946 }
947 else {
948 builder_.append('\n');
949 state_ = State.BLOCK_SEPARATOR_AT_END;
950 }
951 break;
952 case BLANK_AT_END_AFTER_NEWLINE:
953 builder_.setLength(trimRightPos_ - 1);
954 if (builder_.length() == 0) {
955 state_ = State.EMPTY;
956 }
957 else {
958 builder_.append('\n');
959 state_ = State.BLOCK_SEPARATOR_AT_END;
960 }
961 break;
962 case BLOCK_SEPARATOR_AT_END:
963 break;
964 case NEWLINE_AT_END:
965 case BREAK_AT_END:
966 builder_.setLength(builder_.length() - 1);
967 trimRightPos_ = trimRightPos_ - 1;
968 if (builder_.length() == 0) {
969 state_ = State.EMPTY;
970 }
971 else {
972 builder_.append('\n');
973 state_ = State.BLOCK_SEPARATOR_AT_END;
974 }
975 break;
976 default:
977 builder_.append('\n');
978 state_ = State.BLOCK_SEPARATOR_AT_END;
979 break;
980 }
981 }
982
983
984
985
986
987
988 public void appendBreak(final Mode mode) {
989 builder_.setLength(trimRightPos_);
990
991 builder_.append('\n');
992 state_ = State.BREAK_AT_END;
993 trimRightPos_ = builder_.length();
994 }
995
996
997
998
999 public void appendBlank() {
1000 builder_.append(' ');
1001 state_ = State.BLANK_AT_END;
1002 trimRightPos_ = builder_.length();
1003 }
1004
1005
1006
1007
1008
1009
1010 public void trimRight(final Mode mode) {
1011 if (mode == Mode.PRE) {
1012 switch (state_) {
1013 case BLOCK_SEPARATOR_AT_END:
1014 case NEWLINE_AT_END:
1015 case BREAK_AT_END:
1016 if (trimRightPos_ == builder_.length()) {
1017 trimRightPos_--;
1018 }
1019 break;
1020 default:
1021 break;
1022 }
1023 }
1024
1025 builder_.setLength(trimRightPos_);
1026 state_ = State.DEFAULT;
1027 if (builder_.length() == 0) {
1028 state_ = State.EMPTY;
1029 }
1030 }
1031
1032
1033
1034
1035 public boolean wasContentAdded() {
1036 return contentAdded_;
1037 }
1038
1039
1040
1041
1042 public void resetContentAdded() {
1043 contentAdded_ = false;
1044 }
1045
1046
1047
1048
1049 public String getText() {
1050 return builder_.substring(0, trimRightPos_);
1051 }
1052 }
1053 }