1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.yaml.snakeyaml.emitter;
18
19 import java.io.IOException;
20 import java.io.Writer;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.LinkedHashMap;
24 import java.util.Map;
25 import java.util.Queue;
26 import java.util.Set;
27 import java.util.TreeSet;
28 import java.util.concurrent.ArrayBlockingQueue;
29 import java.util.regex.Pattern;
30
31 import org.yaml.snakeyaml.DumperOptions;
32 import org.yaml.snakeyaml.DumperOptions.ScalarStyle;
33 import org.yaml.snakeyaml.events.AliasEvent;
34 import org.yaml.snakeyaml.events.CollectionEndEvent;
35 import org.yaml.snakeyaml.events.CollectionStartEvent;
36 import org.yaml.snakeyaml.events.DocumentEndEvent;
37 import org.yaml.snakeyaml.events.DocumentStartEvent;
38 import org.yaml.snakeyaml.events.Event;
39 import org.yaml.snakeyaml.events.MappingEndEvent;
40 import org.yaml.snakeyaml.events.MappingStartEvent;
41 import org.yaml.snakeyaml.events.NodeEvent;
42 import org.yaml.snakeyaml.events.ScalarEvent;
43 import org.yaml.snakeyaml.events.SequenceEndEvent;
44 import org.yaml.snakeyaml.events.SequenceStartEvent;
45 import org.yaml.snakeyaml.events.StreamEndEvent;
46 import org.yaml.snakeyaml.events.StreamStartEvent;
47 import org.yaml.snakeyaml.nodes.Tag;
48 import org.yaml.snakeyaml.scanner.Constant;
49 import org.yaml.snakeyaml.util.ArrayStack;
50
51
52
53
54
55
56
57
58
59
60
61 public final class Emitter implements Emitable {
62 private static final Map<Character, String> ESCAPE_REPLACEMENTS = new HashMap<Character, String>();
63 public static final int MIN_INDENT = 1;
64 public static final int MAX_INDENT = 10;
65
66 public static final char[] SPACE = { ' ' };
67
68 static {
69 ESCAPE_REPLACEMENTS.put(new Character('\0'), "0");
70 ESCAPE_REPLACEMENTS.put(new Character('\u0007'), "a");
71 ESCAPE_REPLACEMENTS.put(new Character('\u0008'), "b");
72 ESCAPE_REPLACEMENTS.put(new Character('\u0009'), "t");
73 ESCAPE_REPLACEMENTS.put(new Character('\n'), "n");
74 ESCAPE_REPLACEMENTS.put(new Character('\u000B'), "v");
75 ESCAPE_REPLACEMENTS.put(new Character('\u000C'), "f");
76 ESCAPE_REPLACEMENTS.put(new Character('\r'), "r");
77 ESCAPE_REPLACEMENTS.put(new Character('\u001B'), "e");
78 ESCAPE_REPLACEMENTS.put(new Character('"'), "\"");
79 ESCAPE_REPLACEMENTS.put(new Character('\\'), "\\");
80 ESCAPE_REPLACEMENTS.put(new Character('\u0085'), "N");
81 ESCAPE_REPLACEMENTS.put(new Character('\u00A0'), "_");
82 ESCAPE_REPLACEMENTS.put(new Character('\u2028'), "L");
83 ESCAPE_REPLACEMENTS.put(new Character('\u2029'), "P");
84 }
85
86 private final static Map<String, String> DEFAULT_TAG_PREFIXES = new LinkedHashMap<String, String>();
87 static {
88 DEFAULT_TAG_PREFIXES.put("!", "!");
89 DEFAULT_TAG_PREFIXES.put(Tag.PREFIX, "!!");
90 }
91
92 private final Writer stream;
93
94
95
96
97
98
99 private final ArrayStack<EmitterState> states;
100 private EmitterState state;
101
102
103 private final Queue<Event> events;
104 private Event event;
105
106
107 private final ArrayStack<Integer> indents;
108 private Integer indent;
109
110
111 private int flowLevel;
112
113
114 private boolean rootContext;
115 private boolean mappingContext;
116 private boolean simpleKeyContext;
117
118
119
120
121
122
123
124
125 private int column;
126 private boolean whitespace;
127 private boolean indention;
128 private boolean openEnded;
129
130
131 private Boolean canonical;
132
133 private Boolean prettyFlow;
134
135 private boolean allowUnicode;
136 private int bestIndent;
137 private int bestWidth;
138 private char[] bestLineBreak;
139
140
141 private Map<String, String> tagPrefixes;
142
143
144 private String preparedAnchor;
145 private String preparedTag;
146
147
148 private ScalarAnalysis analysis;
149 private Character style;
150 private DumperOptions options;
151
152 public Emitter(Writer stream, DumperOptions opts) {
153
154 this.stream = stream;
155
156
157 this.states = new ArrayStack<EmitterState>(100);
158 this.state = new ExpectStreamStart();
159
160 this.events = new ArrayBlockingQueue<Event>(100);
161 this.event = null;
162
163 this.indents = new ArrayStack<Integer>(10);
164 this.indent = null;
165
166 this.flowLevel = 0;
167
168 mappingContext = false;
169 simpleKeyContext = false;
170
171
172
173
174
175
176
177 column = 0;
178 whitespace = true;
179 indention = true;
180
181
182 openEnded = false;
183
184
185 this.canonical = opts.isCanonical();
186 this.prettyFlow = opts.isPrettyFlow();
187 this.allowUnicode = opts.isAllowUnicode();
188 this.bestIndent = 2;
189 if ((opts.getIndent() > MIN_INDENT) && (opts.getIndent() < MAX_INDENT)) {
190 this.bestIndent = opts.getIndent();
191 }
192 this.bestWidth = 80;
193 if (opts.getWidth() > this.bestIndent * 2) {
194 this.bestWidth = opts.getWidth();
195 }
196 this.bestLineBreak = opts.getLineBreak().getString().toCharArray();
197
198
199 this.tagPrefixes = new LinkedHashMap<String, String>();
200
201
202 this.preparedAnchor = null;
203 this.preparedTag = null;
204
205
206 this.analysis = null;
207 this.style = null;
208 this.options = opts;
209 }
210
211 public void emit(Event event) throws IOException {
212 this.events.add(event);
213 while (!needMoreEvents()) {
214 this.event = this.events.poll();
215 this.state.expect();
216 this.event = null;
217 }
218 }
219
220
221
222 private boolean needMoreEvents() {
223 if (events.isEmpty()) {
224 return true;
225 }
226 Event event = events.peek();
227 if (event instanceof DocumentStartEvent) {
228 return needEvents(1);
229 } else if (event instanceof SequenceStartEvent) {
230 return needEvents(2);
231 } else if (event instanceof MappingStartEvent) {
232 return needEvents(3);
233 } else {
234 return false;
235 }
236 }
237
238 private boolean needEvents(int count) {
239 int level = 0;
240 Iterator<Event> iter = events.iterator();
241 iter.next();
242 while (iter.hasNext()) {
243 Event event = iter.next();
244 if (event instanceof DocumentStartEvent || event instanceof CollectionStartEvent) {
245 level++;
246 } else if (event instanceof DocumentEndEvent || event instanceof CollectionEndEvent) {
247 level--;
248 } else if (event instanceof StreamEndEvent) {
249 level = -1;
250 }
251 if (level < 0) {
252 return false;
253 }
254 }
255 return events.size() < count + 1;
256 }
257
258 private void increaseIndent(boolean flow, boolean indentless) {
259 indents.push(indent);
260 if (indent == null) {
261 if (flow) {
262 indent = bestIndent;
263 } else {
264 indent = 0;
265 }
266 } else if (!indentless) {
267 this.indent += bestIndent;
268 }
269 }
270
271
272
273
274
275 private class ExpectStreamStart implements EmitterState {
276 public void expect() throws IOException {
277 if (event instanceof StreamStartEvent) {
278 writeStreamStart();
279 state = new ExpectFirstDocumentStart();
280 } else {
281 throw new EmitterException("expected StreamStartEvent, but got " + event);
282 }
283 }
284 }
285
286 private class ExpectNothing implements EmitterState {
287 public void expect() throws IOException {
288 throw new EmitterException("expecting nothing, but got " + event);
289 }
290 }
291
292
293
294 private class ExpectFirstDocumentStart implements EmitterState {
295 public void expect() throws IOException {
296 new ExpectDocumentStart(true).expect();
297 }
298 }
299
300 private class ExpectDocumentStart implements EmitterState {
301 private boolean first;
302
303 public ExpectDocumentStart(boolean first) {
304 this.first = first;
305 }
306
307 public void expect() throws IOException {
308 if (event instanceof DocumentStartEvent) {
309 DocumentStartEvent ev = (DocumentStartEvent) event;
310 if ((ev.getVersion() != null || ev.getTags() != null) && openEnded) {
311 writeIndicator("...", true, false, false);
312 writeIndent();
313 }
314 if (ev.getVersion() != null) {
315 String versionText = prepareVersion(ev.getVersion());
316 writeVersionDirective(versionText);
317 }
318 tagPrefixes = new LinkedHashMap<String, String>(DEFAULT_TAG_PREFIXES);
319 if (ev.getTags() != null) {
320 Set<String> handles = new TreeSet<String>(ev.getTags().keySet());
321 for (String handle : handles) {
322 String prefix = ev.getTags().get(handle);
323 tagPrefixes.put(prefix, handle);
324 String handleText = prepareTagHandle(handle);
325 String prefixText = prepareTagPrefix(prefix);
326 writeTagDirective(handleText, prefixText);
327 }
328 }
329 boolean implicit = first && !ev.getExplicit() && !canonical
330 && ev.getVersion() == null && ev.getTags() == null && !checkEmptyDocument();
331 if (!implicit) {
332 writeIndent();
333 writeIndicator("---", true, false, false);
334 if (canonical) {
335 writeIndent();
336 }
337 }
338 state = new ExpectDocumentRoot();
339 } else if (event instanceof StreamEndEvent) {
340
341
342
343
344
345 writeStreamEnd();
346 state = new ExpectNothing();
347 } else {
348 throw new EmitterException("expected DocumentStartEvent, but got " + event);
349 }
350 }
351 }
352
353 private class ExpectDocumentEnd implements EmitterState {
354 public void expect() throws IOException {
355 if (event instanceof DocumentEndEvent) {
356 writeIndent();
357 if (((DocumentEndEvent) event).getExplicit()) {
358 writeIndicator("...", true, false, false);
359 writeIndent();
360 }
361 flushStream();
362 state = new ExpectDocumentStart(false);
363 } else {
364 throw new EmitterException("expected DocumentEndEvent, but got " + event);
365 }
366 }
367 }
368
369 private class ExpectDocumentRoot implements EmitterState {
370 public void expect() throws IOException {
371 states.push(new ExpectDocumentEnd());
372 expectNode(true, false, false, false);
373 }
374 }
375
376
377
378 private void expectNode(boolean root, boolean sequence, boolean mapping, boolean simpleKey)
379 throws IOException {
380 rootContext = root;
381 mappingContext = mapping;
382 simpleKeyContext = simpleKey;
383 if (event instanceof AliasEvent) {
384 expectAlias();
385 } else if (event instanceof ScalarEvent || event instanceof CollectionStartEvent) {
386 processAnchor("&");
387 processTag();
388 if (event instanceof ScalarEvent) {
389 expectScalar();
390 } else if (event instanceof SequenceStartEvent) {
391 if (flowLevel != 0 || canonical || ((SequenceStartEvent) event).getFlowStyle()
392 || checkEmptySequence()) {
393 expectFlowSequence();
394 } else {
395 expectBlockSequence();
396 }
397 } else {
398 if (flowLevel != 0 || canonical || ((MappingStartEvent) event).getFlowStyle()
399 || checkEmptyMapping()) {
400 expectFlowMapping();
401 } else {
402 expectBlockMapping();
403 }
404 }
405 } else {
406 throw new EmitterException("expected NodeEvent, but got " + event);
407 }
408 }
409
410 private void expectAlias() throws IOException {
411 if (((NodeEvent) event).getAnchor() == null) {
412 throw new EmitterException("anchor is not specified for alias");
413 }
414 processAnchor("*");
415 state = states.pop();
416 }
417
418 private void expectScalar() throws IOException {
419 increaseIndent(true, false);
420 processScalar();
421 indent = indents.pop();
422 state = states.pop();
423 }
424
425
426
427 private void expectFlowSequence() throws IOException {
428 writeIndicator("[", true, true, false);
429 flowLevel++;
430 increaseIndent(true, false);
431 if (prettyFlow) {
432 writeIndent();
433 }
434 state = new ExpectFirstFlowSequenceItem();
435 }
436
437 private class ExpectFirstFlowSequenceItem implements EmitterState {
438 public void expect() throws IOException {
439 if (event instanceof SequenceEndEvent) {
440 indent = indents.pop();
441 flowLevel--;
442 writeIndicator("]", false, false, false);
443 state = states.pop();
444 } else {
445 if (canonical || column > bestWidth || prettyFlow) {
446 writeIndent();
447 }
448 states.push(new ExpectFlowSequenceItem());
449 expectNode(false, true, false, false);
450 }
451 }
452 }
453
454 private class ExpectFlowSequenceItem implements EmitterState {
455 public void expect() throws IOException {
456 if (event instanceof SequenceEndEvent) {
457 indent = indents.pop();
458 flowLevel--;
459 if (canonical) {
460 writeIndicator(",", false, false, false);
461 writeIndent();
462 }
463 writeIndicator("]", false, false, false);
464 if (prettyFlow) {
465 writeIndent();
466 }
467 state = states.pop();
468 } else {
469 writeIndicator(",", false, false, false);
470 if (canonical || column > bestWidth || prettyFlow) {
471 writeIndent();
472 }
473 states.push(new ExpectFlowSequenceItem());
474 expectNode(false, true, false, false);
475 }
476 }
477 }
478
479
480
481 private void expectFlowMapping() throws IOException {
482 writeIndicator("{", true, true, false);
483 flowLevel++;
484 increaseIndent(true, false);
485 if (prettyFlow) {
486 writeIndent();
487 }
488 state = new ExpectFirstFlowMappingKey();
489 }
490
491 private class ExpectFirstFlowMappingKey implements EmitterState {
492 public void expect() throws IOException {
493 if (event instanceof MappingEndEvent) {
494 indent = indents.pop();
495 flowLevel--;
496 writeIndicator("}", false, false, false);
497 state = states.pop();
498 } else {
499 if (canonical || column > bestWidth || prettyFlow) {
500 writeIndent();
501 }
502 if (!canonical && checkSimpleKey()) {
503 states.push(new ExpectFlowMappingSimpleValue());
504 expectNode(false, false, true, true);
505 } else {
506 writeIndicator("?", true, false, false);
507 states.push(new ExpectFlowMappingValue());
508 expectNode(false, false, true, false);
509 }
510 }
511 }
512 }
513
514 private class ExpectFlowMappingKey implements EmitterState {
515 public void expect() throws IOException {
516 if (event instanceof MappingEndEvent) {
517 indent = indents.pop();
518 flowLevel--;
519 if (canonical) {
520 writeIndicator(",", false, false, false);
521 writeIndent();
522 }
523 if (prettyFlow) {
524 writeIndent();
525 }
526 writeIndicator("}", false, false, false);
527 state = states.pop();
528 } else {
529 writeIndicator(",", false, false, false);
530 if (canonical || column > bestWidth || prettyFlow) {
531 writeIndent();
532 }
533 if (!canonical && checkSimpleKey()) {
534 states.push(new ExpectFlowMappingSimpleValue());
535 expectNode(false, false, true, true);
536 } else {
537 writeIndicator("?", true, false, false);
538 states.push(new ExpectFlowMappingValue());
539 expectNode(false, false, true, false);
540 }
541 }
542 }
543 }
544
545 private class ExpectFlowMappingSimpleValue implements EmitterState {
546 public void expect() throws IOException {
547 writeIndicator(":", false, false, false);
548 states.push(new ExpectFlowMappingKey());
549 expectNode(false, false, true, false);
550 }
551 }
552
553 private class ExpectFlowMappingValue implements EmitterState {
554 public void expect() throws IOException {
555 if (canonical || column > bestWidth || prettyFlow) {
556 writeIndent();
557 }
558 writeIndicator(":", true, false, false);
559 states.push(new ExpectFlowMappingKey());
560 expectNode(false, false, true, false);
561 }
562 }
563
564
565
566 private void expectBlockSequence() throws IOException {
567 boolean indentless = (mappingContext && !indention);
568 increaseIndent(false, indentless);
569 state = new ExpectFirstBlockSequenceItem();
570 }
571
572 private class ExpectFirstBlockSequenceItem implements EmitterState {
573 public void expect() throws IOException {
574 new ExpectBlockSequenceItem(true).expect();
575 }
576 }
577
578 private class ExpectBlockSequenceItem implements EmitterState {
579 private boolean first;
580
581 public ExpectBlockSequenceItem(boolean first) {
582 this.first = first;
583 }
584
585 public void expect() throws IOException {
586 if (!this.first && event instanceof SequenceEndEvent) {
587 indent = indents.pop();
588 state = states.pop();
589 } else {
590 writeIndent();
591 writeIndicator("-", true, false, true);
592 states.push(new ExpectBlockSequenceItem(false));
593 expectNode(false, true, false, false);
594 }
595 }
596 }
597
598
599 private void expectBlockMapping() throws IOException {
600 increaseIndent(false, false);
601 state = new ExpectFirstBlockMappingKey();
602 }
603
604 private class ExpectFirstBlockMappingKey implements EmitterState {
605 public void expect() throws IOException {
606 new ExpectBlockMappingKey(true).expect();
607 }
608 }
609
610 private class ExpectBlockMappingKey implements EmitterState {
611 private boolean first;
612
613 public ExpectBlockMappingKey(boolean first) {
614 this.first = first;
615 }
616
617 public void expect() throws IOException {
618 if (!this.first && event instanceof MappingEndEvent) {
619 indent = indents.pop();
620 state = states.pop();
621 } else {
622 writeIndent();
623 if (checkSimpleKey()) {
624 states.push(new ExpectBlockMappingSimpleValue());
625 expectNode(false, false, true, true);
626 } else {
627 writeIndicator("?", true, false, true);
628 states.push(new ExpectBlockMappingValue());
629 expectNode(false, false, true, false);
630 }
631 }
632 }
633 }
634
635 private class ExpectBlockMappingSimpleValue implements EmitterState {
636 public void expect() throws IOException {
637 writeIndicator(":", false, false, false);
638 states.push(new ExpectBlockMappingKey(false));
639 expectNode(false, false, true, false);
640 }
641 }
642
643 private class ExpectBlockMappingValue implements EmitterState {
644 public void expect() throws IOException {
645 writeIndent();
646 writeIndicator(":", true, false, true);
647 states.push(new ExpectBlockMappingKey(false));
648 expectNode(false, false, true, false);
649 }
650 }
651
652
653
654 private boolean checkEmptySequence() {
655 return (event instanceof SequenceStartEvent && !events.isEmpty() && events.peek() instanceof SequenceEndEvent);
656 }
657
658 private boolean checkEmptyMapping() {
659 return (event instanceof MappingStartEvent && !events.isEmpty() && events.peek() instanceof MappingEndEvent);
660 }
661
662 private boolean checkEmptyDocument() {
663 if (!(event instanceof DocumentStartEvent) || events.isEmpty()) {
664 return false;
665 }
666 Event event = events.peek();
667 if (event instanceof ScalarEvent) {
668 ScalarEvent e = (ScalarEvent) event;
669 return (e.getAnchor() == null && e.getTag() == null && e.getImplicit() != null && e
670 .getValue() == "");
671 }
672 return false;
673 }
674
675 private boolean checkSimpleKey() {
676 int length = 0;
677 if (event instanceof NodeEvent && ((NodeEvent) event).getAnchor() != null) {
678 if (preparedAnchor == null) {
679 preparedAnchor = prepareAnchor(((NodeEvent) event).getAnchor());
680 }
681 length += preparedAnchor.length();
682 }
683 String tag = null;
684 if (event instanceof ScalarEvent) {
685 tag = ((ScalarEvent) event).getTag();
686 } else if (event instanceof CollectionStartEvent) {
687 tag = ((CollectionStartEvent) event).getTag();
688 }
689 if (tag != null) {
690 if (preparedTag == null) {
691 preparedTag = prepareTag(tag);
692 }
693 length += preparedTag.length();
694 }
695 if (event instanceof ScalarEvent) {
696 if (analysis == null) {
697 analysis = analyzeScalar(((ScalarEvent) event).getValue());
698 }
699 length += analysis.scalar.length();
700 }
701 return (length < 128 && (event instanceof AliasEvent
702 || (event instanceof ScalarEvent && !analysis.empty && !analysis.multiline)
703 || checkEmptySequence() || checkEmptyMapping()));
704 }
705
706
707
708 private void processAnchor(String indicator) throws IOException {
709 NodeEvent ev = (NodeEvent) event;
710 if (ev.getAnchor() == null) {
711 preparedAnchor = null;
712 return;
713 }
714 if (preparedAnchor == null) {
715 preparedAnchor = prepareAnchor(ev.getAnchor());
716 }
717 writeIndicator(indicator + preparedAnchor, true, false, false);
718 preparedAnchor = null;
719 }
720
721 private void processTag() throws IOException {
722 String tag = null;
723 if (event instanceof ScalarEvent) {
724 ScalarEvent ev = (ScalarEvent) event;
725 tag = ev.getTag();
726 if (style == null) {
727 style = chooseScalarStyle();
728 }
729 if (((!canonical || tag == null) && ((style == null && ev.getImplicit()
730 .canOmitTagInPlainScalar()) || (style != null && ev.getImplicit()
731 .canOmitTagInNonPlainScalar())))) {
732 preparedTag = null;
733 return;
734 }
735 if (ev.getImplicit().canOmitTagInPlainScalar() && tag == null) {
736 tag = "!";
737 preparedTag = null;
738 }
739 } else {
740 CollectionStartEvent ev = (CollectionStartEvent) event;
741 tag = ev.getTag();
742 if ((!canonical || tag == null) && ev.getImplicit()) {
743 preparedTag = null;
744 return;
745 }
746 }
747 if (tag == null) {
748 throw new EmitterException("tag is not specified");
749 }
750 if (preparedTag == null) {
751 preparedTag = prepareTag(tag);
752 }
753 writeIndicator(preparedTag, true, false, false);
754 preparedTag = null;
755 }
756
757 private Character chooseScalarStyle() {
758 ScalarEvent ev = (ScalarEvent) event;
759 if (analysis == null) {
760 analysis = analyzeScalar(ev.getValue());
761 }
762 if (ev.getStyle() != null && ev.getStyle() == '"' || this.canonical) {
763 return '"';
764 }
765 if (ev.getStyle() == null && ev.getImplicit().canOmitTagInPlainScalar()) {
766 if (!(simpleKeyContext && (analysis.empty || analysis.multiline))
767 && ((flowLevel != 0 && analysis.allowFlowPlain) || (flowLevel == 0 && analysis.allowBlockPlain))) {
768 return null;
769 }
770 }
771 if (ev.getStyle() != null && (ev.getStyle() == '|' || ev.getStyle() == '>')) {
772 if (flowLevel == 0 && !simpleKeyContext && analysis.allowBlock) {
773 return ev.getStyle();
774 }
775 }
776 if (ev.getStyle() == null || ev.getStyle() == '\'') {
777 if (analysis.allowSingleQuoted && !(simpleKeyContext && analysis.multiline)) {
778 return '\'';
779 }
780 }
781 return '"';
782 }
783
784 private void processScalar() throws IOException {
785 ScalarEvent ev = (ScalarEvent) event;
786 if (analysis == null) {
787 analysis = analyzeScalar(ev.getValue());
788 }
789 if (style == null) {
790 style = chooseScalarStyle();
791 }
792 style = options.calculateScalarStyle(analysis, ScalarStyle.createStyle(style)).getChar();
793 boolean split = !simpleKeyContext;
794 if (style == null) {
795 writePlain(analysis.scalar, split);
796 } else {
797 switch (style) {
798 case '"':
799 writeDoubleQuoted(analysis.scalar, split);
800 break;
801 case '\'':
802 writeSingleQuoted(analysis.scalar, split);
803 break;
804 case '>':
805 writeFolded(analysis.scalar);
806 break;
807 case '|':
808 writeLiteral(analysis.scalar);
809 break;
810 }
811 }
812 analysis = null;
813 style = null;
814 }
815
816
817
818 private String prepareVersion(Integer[] version) {
819 Integer major = version[0];
820 Integer minor = version[1];
821 if (major != 1) {
822 throw new EmitterException("unsupported YAML version: " + version[0] + "." + version[1]);
823 }
824 return major.toString() + "." + minor.toString();
825 }
826
827 private final static Pattern HANDLE_FORMAT = Pattern.compile("^![-_\\w]*!$");
828
829 private String prepareTagHandle(String handle) {
830 if (handle.length() == 0) {
831 throw new EmitterException("tag handle must not be empty");
832 } else if (handle.charAt(0) != '!' || handle.charAt(handle.length() - 1) != '!') {
833 throw new EmitterException("tag handle must start and end with '!': " + handle);
834 } else if (!"!".equals(handle) && !HANDLE_FORMAT.matcher(handle).matches()) {
835 throw new EmitterException("invalid character in the tag handle: " + handle);
836 }
837 return handle;
838 }
839
840 private String prepareTagPrefix(String prefix) {
841 if (prefix.length() == 0) {
842 throw new EmitterException("tag prefix must not be empty");
843 }
844 StringBuilder chunks = new StringBuilder();
845 int start = 0;
846 int end = 0;
847 if (prefix.charAt(0) == '!') {
848 end = 1;
849 }
850 while (end < prefix.length()) {
851 end++;
852 }
853 if (start < end) {
854 chunks.append(prefix.substring(start, end));
855 }
856 return chunks.toString();
857 }
858
859 private String prepareTag(String tag) {
860 if (tag.length() == 0) {
861 throw new EmitterException("tag must not be empty");
862 }
863 if ("!".equals(tag)) {
864 return tag;
865 }
866 String handle = null;
867 String suffix = tag;
868 for (String prefix : tagPrefixes.keySet()) {
869 if (tag.startsWith(prefix) && ("!".equals(prefix) || prefix.length() < tag.length())) {
870 handle = prefix;
871 }
872 }
873 if (handle != null) {
874 suffix = tag.substring(handle.length());
875 handle = tagPrefixes.get(handle);
876 }
877
878 int end = suffix.length();
879 String suffixText = end > 0 ? suffix.substring(0, end) : "";
880
881 if (handle != null) {
882 return handle + suffixText;
883 }
884 return "!<" + suffixText + ">";
885 }
886
887 private final static Pattern ANCHOR_FORMAT = Pattern.compile("^[-_\\w]*$");
888
889 static String prepareAnchor(String anchor) {
890 if (anchor.length() == 0) {
891 throw new EmitterException("anchor must not be empty");
892 }
893 if (!ANCHOR_FORMAT.matcher(anchor).matches()) {
894 throw new EmitterException("invalid character in the anchor: " + anchor);
895 }
896 return anchor;
897 }
898
899 private ScalarAnalysis analyzeScalar(String scalar) {
900
901 if (scalar.length() == 0) {
902 return new ScalarAnalysis(scalar, true, false, false, true, true, true, false);
903 }
904
905 boolean blockIndicators = false;
906 boolean flowIndicators = false;
907 boolean lineBreaks = false;
908 boolean specialCharacters = false;
909
910
911 boolean leadingSpace = false;
912 boolean leadingBreak = false;
913 boolean trailingSpace = false;
914 boolean trailingBreak = false;
915 boolean breakSpace = false;
916 boolean spaceBreak = false;
917
918
919 if (scalar.startsWith("---") || scalar.startsWith("...")) {
920 blockIndicators = true;
921 flowIndicators = true;
922 }
923
924 boolean preceededByWhitespace = true;
925 boolean followedByWhitespace = (scalar.length() == 1 || Constant.NULL_BL_T_LINEBR
926 .has(scalar.charAt(1)));
927
928 boolean previousSpace = false;
929
930
931 boolean previousBreak = false;
932
933 int index = 0;
934
935 while (index < scalar.length()) {
936 char ch = scalar.charAt(index);
937
938 if (index == 0) {
939
940 if ("#,[]{}&*!|>\'\"%@`".indexOf(ch) != -1) {
941 flowIndicators = true;
942 blockIndicators = true;
943 }
944 if (ch == '?' || ch == ':') {
945 flowIndicators = true;
946 if (followedByWhitespace) {
947 blockIndicators = true;
948 }
949 }
950 if (ch == '-' && followedByWhitespace) {
951 flowIndicators = true;
952 blockIndicators = true;
953 }
954 } else {
955
956 if (",?[]{}".indexOf(ch) != -1) {
957 flowIndicators = true;
958 }
959 if (ch == ':') {
960 flowIndicators = true;
961 if (followedByWhitespace) {
962 blockIndicators = true;
963 }
964 }
965 if (ch == '#' && preceededByWhitespace) {
966 flowIndicators = true;
967 blockIndicators = true;
968 }
969 }
970
971 boolean isLineBreak = Constant.LINEBR.has(ch);
972 if (isLineBreak) {
973 lineBreaks = true;
974 }
975 if (!(ch == '\n' || ('\u0020' <= ch && ch <= '\u007E'))) {
976 if ((ch == '\u0085' || ('\u00A0' <= ch && ch <= '\uD7FF') || ('\uE000' <= ch && ch <= '\uFFFD'))
977 && (ch != '\uFEFF')) {
978
979 if (!this.allowUnicode) {
980 specialCharacters = true;
981 }
982 } else {
983 specialCharacters = true;
984 }
985 }
986
987 if (ch == ' ') {
988 if (index == 0) {
989 leadingSpace = true;
990 }
991 if (index == scalar.length() - 1) {
992 trailingSpace = true;
993 }
994 if (previousBreak) {
995 breakSpace = true;
996 }
997 previousSpace = true;
998 previousBreak = false;
999 } else if (isLineBreak) {
1000 if (index == 0) {
1001 leadingBreak = true;
1002 }
1003 if (index == scalar.length() - 1) {
1004 trailingBreak = true;
1005 }
1006 if (previousSpace) {
1007 spaceBreak = true;
1008 }
1009 previousSpace = false;
1010 previousBreak = true;
1011 } else {
1012 previousSpace = false;
1013 previousBreak = false;
1014 }
1015
1016
1017 index++;
1018 preceededByWhitespace = Constant.NULL_BL_T.has(ch) || isLineBreak;
1019 followedByWhitespace = (index + 1 >= scalar.length()
1020 || (Constant.NULL_BL_T.has(scalar.charAt(index + 1))) || isLineBreak);
1021 }
1022
1023 boolean allowFlowPlain = true;
1024 boolean allowBlockPlain = true;
1025 boolean allowSingleQuoted = true;
1026 boolean allowDoubleQuoted = true;
1027 boolean allowBlock = true;
1028
1029 if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) {
1030 allowFlowPlain = allowBlockPlain = false;
1031 }
1032
1033 if (trailingSpace) {
1034 allowBlock = false;
1035 }
1036
1037
1038 if (breakSpace) {
1039 allowFlowPlain = allowBlockPlain = allowSingleQuoted = false;
1040 }
1041
1042
1043 if (spaceBreak || specialCharacters) {
1044 allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false;
1045 }
1046
1047
1048 if (lineBreaks) {
1049 allowFlowPlain = allowBlockPlain = false;
1050 }
1051
1052 if (flowIndicators) {
1053 allowFlowPlain = false;
1054 }
1055
1056 if (blockIndicators) {
1057 allowBlockPlain = false;
1058 }
1059
1060 return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain,
1061 allowSingleQuoted, allowDoubleQuoted, allowBlock);
1062 }
1063
1064
1065
1066 void flushStream() throws IOException {
1067 stream.flush();
1068 }
1069
1070 void writeStreamStart() {
1071
1072 }
1073
1074 void writeStreamEnd() throws IOException {
1075 flushStream();
1076 }
1077
1078 void writeIndicator(String indicator, boolean needWhitespace, boolean whitespace,
1079 boolean indentation) throws IOException {
1080 if (!this.whitespace && needWhitespace) {
1081 this.column++;
1082 stream.write(SPACE);
1083 }
1084 this.whitespace = whitespace;
1085 this.indention = this.indention && indentation;
1086 this.column += indicator.length();
1087 openEnded = false;
1088 stream.write(indicator);
1089 }
1090
1091 void writeIndent() throws IOException {
1092 int indent;
1093 if (this.indent != null) {
1094 indent = this.indent;
1095 } else {
1096 indent = 0;
1097 }
1098
1099 if (!this.indention || this.column > indent || (this.column == indent && !this.whitespace)) {
1100 writeLineBreak(null);
1101 }
1102
1103 if (this.column < indent) {
1104 this.whitespace = true;
1105 char[] data = new char[indent - this.column];
1106 for (int i = 0; i < data.length; i++) {
1107 data[i] = ' ';
1108 }
1109 this.column = indent;
1110 stream.write(data);
1111 }
1112 }
1113
1114 private void writeLineBreak(String data) throws IOException {
1115 this.whitespace = true;
1116 this.indention = true;
1117 this.column = 0;
1118 if (data == null) {
1119 stream.write(this.bestLineBreak);
1120 } else {
1121 stream.write(data);
1122 }
1123 }
1124
1125 void writeVersionDirective(String versionText) throws IOException {
1126 stream.write("%YAML ");
1127 stream.write(versionText);
1128 writeLineBreak(null);
1129 }
1130
1131 void writeTagDirective(String handleText, String prefixText) throws IOException {
1132
1133
1134 stream.write("%TAG ");
1135 stream.write(handleText);
1136 stream.write(SPACE);
1137 stream.write(prefixText);
1138 writeLineBreak(null);
1139 }
1140
1141
1142 private void writeSingleQuoted(String text, boolean split) throws IOException {
1143 writeIndicator("'", true, false, false);
1144 boolean spaces = false;
1145 boolean breaks = false;
1146 int start = 0, end = 0;
1147 char ch;
1148 while (end <= text.length()) {
1149 ch = 0;
1150 if (end < text.length()) {
1151 ch = text.charAt(end);
1152 }
1153 if (spaces) {
1154 if (ch == 0 || ch != ' ') {
1155 if (start + 1 == end && this.column > this.bestWidth && split && start != 0
1156 && end != text.length()) {
1157 writeIndent();
1158 } else {
1159 int len = end - start;
1160 this.column += len;
1161 stream.write(text, start, len);
1162 }
1163 start = end;
1164 }
1165 } else if (breaks) {
1166 if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
1167 if (text.charAt(start) == '\n') {
1168 writeLineBreak(null);
1169 }
1170 String data = text.substring(start, end);
1171 for (char br : data.toCharArray()) {
1172 if (br == '\n') {
1173 writeLineBreak(null);
1174 } else {
1175 writeLineBreak(String.valueOf(br));
1176 }
1177 }
1178 writeIndent();
1179 start = end;
1180 }
1181 } else {
1182 if (Constant.LINEBR.has(ch, "\0 \'")) {
1183 if (start < end) {
1184 int len = end - start;
1185 this.column += len;
1186 stream.write(text, start, len);
1187 start = end;
1188 }
1189 }
1190 }
1191 if (ch == '\'') {
1192 this.column += 2;
1193 stream.write("''");
1194 start = end + 1;
1195 }
1196 if (ch != 0) {
1197 spaces = ch == ' ';
1198 breaks = Constant.LINEBR.has(ch);
1199 }
1200 end++;
1201 }
1202 writeIndicator("'", false, false, false);
1203 }
1204
1205 private void writeDoubleQuoted(String text, boolean split) throws IOException {
1206 writeIndicator("\"", true, false, false);
1207 int start = 0;
1208 int end = 0;
1209 while (end <= text.length()) {
1210 Character ch = null;
1211 if (end < text.length()) {
1212 ch = text.charAt(end);
1213 }
1214 if (ch == null || "\"\\\u0085\u2028\u2029\uFEFF".indexOf(ch) != -1
1215 || !('\u0020' <= ch && ch <= '\u007E')) {
1216 if (start < end) {
1217 int len = end - start;
1218 this.column += len;
1219 stream.write(text, start, len);
1220 start = end;
1221 }
1222 if (ch != null) {
1223 String data;
1224 if (ESCAPE_REPLACEMENTS.containsKey(new Character(ch))) {
1225 data = "\\" + ESCAPE_REPLACEMENTS.get(new Character(ch));
1226 } else if (!this.allowUnicode) {
1227
1228
1229 if (ch <= '\u00FF') {
1230 String s = "0" + Integer.toString(ch, 16);
1231 data = "\\x" + s.substring(s.length() - 2);
1232 } else {
1233 String s = "000" + Integer.toString(ch, 16);
1234 data = "\\u" + s.substring(s.length() - 4);
1235 }
1236 } else {
1237 data = String.valueOf(ch);
1238 }
1239 this.column += data.length();
1240 stream.write(data);
1241 start = end + 1;
1242 }
1243 }
1244 if ((0 < end && end < (text.length() - 1)) && (ch == ' ' || start >= end)
1245 && (this.column + (end - start)) > this.bestWidth && split) {
1246 String data;
1247 if (start >= end) {
1248 data = "\\";
1249 } else {
1250 data = text.substring(start, end) + "\\";
1251 }
1252 if (start < end) {
1253 start = end;
1254 }
1255 this.column += data.length();
1256 stream.write(data);
1257 writeIndent();
1258 this.whitespace = false;
1259 this.indention = false;
1260 if (text.charAt(start) == ' ') {
1261 data = "\\";
1262 this.column += data.length();
1263 stream.write(data);
1264 }
1265 }
1266 end += 1;
1267 }
1268 writeIndicator("\"", false, false, false);
1269 }
1270
1271 private String determineBlockHints(String text) {
1272 StringBuilder hints = new StringBuilder();
1273 if (Constant.LINEBR.has(text.charAt(0), " ")) {
1274 hints.append(bestIndent);
1275 }
1276 char ch1 = text.charAt(text.length() - 1);
1277 if (Constant.LINEBR.hasNo(ch1)) {
1278 hints.append("-");
1279 } else if (text.length() == 1 || Constant.LINEBR.has(text.charAt(text.length() - 2))) {
1280 hints.append("+");
1281 }
1282 return hints.toString();
1283 }
1284
1285 void writeFolded(String text) throws IOException {
1286 String hints = determineBlockHints(text);
1287 writeIndicator(">" + hints, true, false, false);
1288 if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) {
1289 openEnded = true;
1290 }
1291 writeLineBreak(null);
1292 boolean leadingSpace = true;
1293 boolean spaces = false;
1294 boolean breaks = true;
1295 int start = 0, end = 0;
1296 while (end <= text.length()) {
1297 char ch = 0;
1298 if (end < text.length()) {
1299 ch = text.charAt(end);
1300 }
1301 if (breaks) {
1302 if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
1303 if (!leadingSpace && ch != 0 && ch != ' ' && text.charAt(start) == '\n') {
1304 writeLineBreak(null);
1305 }
1306 leadingSpace = (ch == ' ');
1307 String data = text.substring(start, end);
1308 for (char br : data.toCharArray()) {
1309 if (br == '\n') {
1310 writeLineBreak(null);
1311 } else {
1312 writeLineBreak(String.valueOf(br));
1313 }
1314 }
1315 if (ch != 0) {
1316 writeIndent();
1317 }
1318 start = end;
1319 }
1320 } else if (spaces) {
1321 if (ch != ' ') {
1322 if (start + 1 == end && this.column > this.bestWidth) {
1323 writeIndent();
1324 } else {
1325 int len = end - start;
1326 this.column += len;
1327 stream.write(text, start, len);
1328 }
1329 start = end;
1330 }
1331 } else {
1332 if (Constant.LINEBR.has(ch, "\0 ")) {
1333 int len = end - start;
1334 this.column += len;
1335 stream.write(text, start, len);
1336 if (ch == 0) {
1337 writeLineBreak(null);
1338 }
1339 start = end;
1340 }
1341 }
1342 if (ch != 0) {
1343 breaks = Constant.LINEBR.has(ch);
1344 spaces = (ch == ' ');
1345 }
1346 end++;
1347 }
1348 }
1349
1350 void writeLiteral(String text) throws IOException {
1351 String hints = determineBlockHints(text);
1352 writeIndicator("|" + hints, true, false, false);
1353 if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') {
1354 openEnded = true;
1355 }
1356 writeLineBreak(null);
1357 boolean breaks = true;
1358 int start = 0, end = 0;
1359 while (end <= text.length()) {
1360 char ch = 0;
1361 if (end < text.length()) {
1362 ch = text.charAt(end);
1363 }
1364 if (breaks) {
1365 if (ch == 0 || Constant.LINEBR.hasNo(ch)) {
1366 String data = text.substring(start, end);
1367 for (char br : data.toCharArray()) {
1368 if (br == '\n') {
1369 writeLineBreak(null);
1370 } else {
1371 writeLineBreak(String.valueOf(br));
1372 }
1373 }
1374 if (ch != 0) {
1375 writeIndent();
1376 }
1377 start = end;
1378 }
1379 } else {
1380 if (ch == 0 || Constant.LINEBR.has(ch)) {
1381 stream.write(text, start, end - start);
1382 if (ch == 0) {
1383 writeLineBreak(null);
1384 }
1385 start = end;
1386 }
1387 }
1388 if (ch != 0) {
1389 breaks = (Constant.LINEBR.has(ch));
1390 }
1391 end++;
1392 }
1393 }
1394
1395 void writePlain(String text, boolean split) throws IOException {
1396 if (rootContext) {
1397 openEnded = true;
1398 }
1399 if (text.length() == 0) {
1400 return;
1401 }
1402 if (!this.whitespace) {
1403 this.column++;
1404 stream.write(SPACE);
1405 }
1406 this.whitespace = false;
1407 this.indention = false;
1408 boolean spaces = false;
1409 boolean breaks = false;
1410 int start = 0, end = 0;
1411 while (end <= text.length()) {
1412 char ch = 0;
1413 if (end < text.length()) {
1414 ch = text.charAt(end);
1415 }
1416 if (spaces) {
1417 if (ch != ' ') {
1418 if (start + 1 == end && this.column > this.bestWidth && split) {
1419 writeIndent();
1420 this.whitespace = false;
1421 this.indention = false;
1422 } else {
1423 int len = end - start;
1424 this.column += len;
1425 stream.write(text, start, len);
1426 }
1427 start = end;
1428 }
1429 } else if (breaks) {
1430 if (Constant.LINEBR.hasNo(ch)) {
1431 if (text.charAt(start) == '\n') {
1432 writeLineBreak(null);
1433 }
1434 String data = text.substring(start, end);
1435 for (char br : data.toCharArray()) {
1436 if (br == '\n') {
1437 writeLineBreak(null);
1438 } else {
1439 writeLineBreak(String.valueOf(br));
1440 }
1441 }
1442 writeIndent();
1443 this.whitespace = false;
1444 this.indention = false;
1445 start = end;
1446 }
1447 } else {
1448 if (ch == 0 || Constant.LINEBR.has(ch)) {
1449 int len = end - start;
1450 this.column += len;
1451 stream.write(text, start, len);
1452 start = end;
1453 }
1454 }
1455 if (ch != 0) {
1456 spaces = (ch == ' ');
1457 breaks = (Constant.LINEBR.has(ch));
1458 }
1459 end++;
1460 }
1461 }
1462 }