View Javadoc

    1   /**
    2    * Copyright (c) 2008-2011, http://www.snakeyaml.org
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    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   * <pre>
    53   * Emitter expects events obeying the following grammar:
    54   * stream ::= STREAM-START document* STREAM-END
    55   * document ::= DOCUMENT-START node DOCUMENT-END
    56   * node ::= SCALAR | sequence | mapping
    57   * sequence ::= SEQUENCE-START node* SEQUENCE-END
    58   * mapping ::= MAPPING-START (node node)* MAPPING-END
    59   * </pre>
    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      // The stream should have the methods `write` and possibly `flush`.
    92      private final Writer stream;
    93
    94      // Encoding is defined by Writer (cannot be overriden by STREAM-START.)
    95      // private Charset encoding;
    96
    97      // Emitter is a state machine with a stack of states to handle nested
    98      // structures.
    99      private final ArrayStack<EmitterState> states;
    100     private EmitterState state;
    101
    102     // Current event and the event queue.
    103     private final Queue<Event> events;
    104     private Event event;
    105
    106     // The current indentation level and the stack of previous indents.
    107     private final ArrayStack<Integer> indents;
    108     private Integer indent;
    109
    110     // Flow level.
    111     private int flowLevel;
    112
    113     // Contexts.
    114     private boolean rootContext;
    115     private boolean mappingContext;
    116     private boolean simpleKeyContext;
    117
    118     //
    119     // Characteristics of the last emitted character:
    120     // - current position.
    121     // - is it a whitespace?
    122     // - is it an indention character
    123     // (indentation space, '-', '?', or ':')?
    124     // private int line; this variable is not used
    125     private int column;
    126     private boolean whitespace;
    127     private boolean indention;
    128     private boolean openEnded;
    129
    130     // Formatting details.
    131     private Boolean canonical;
    132     // pretty print flow by adding extra line breaks
    133     private Boolean prettyFlow;
    134
    135     private boolean allowUnicode;
    136     private int bestIndent;
    137     private int bestWidth;
    138     private char[] bestLineBreak;
    139
    140     // Tag prefixes.
    141     private Map<String, String> tagPrefixes;
    142
    143     // Prepared anchor and tag.
    144     private String preparedAnchor;
    145     private String preparedTag;
    146
    147     // Scalar analysis and style.
    148     private ScalarAnalysis analysis;
    149     private Character style;
    150     private DumperOptions options;
    151
    152     public Emitter(Writer stream, DumperOptions opts) {
    153         // The stream should have the methods `write` and possibly `flush`.
    154         this.stream = stream;
    155         // Emitter is a state machine with a stack of states to handle nested
    156         // structures.
    157         this.states = new ArrayStack<EmitterState>(100);
    158         this.state = new ExpectStreamStart();
    159         // Current event and the event queue.
    160         this.events = new ArrayBlockingQueue<Event>(100);
    161         this.event = null;
    162         // The current indentation level and the stack of previous indents.
    163         this.indents = new ArrayStack<Integer>(10);
    164         this.indent = null;
    165         // Flow level.
    166         this.flowLevel = 0;
    167         // Contexts.
    168         mappingContext = false;
    169         simpleKeyContext = false;
    170
    171         //
    172         // Characteristics of the last emitted character:
    173         // - current position.
    174         // - is it a whitespace?
    175         // - is it an indention character
    176         // (indentation space, '-', '?', or ':')?
    177         column = 0;
    178         whitespace = true;
    179         indention = true;
    180
    181         // Whether the document requires an explicit document indicator
    182         openEnded = false;
    183
    184         // Formatting details.
    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         // Tag prefixes.
    199         this.tagPrefixes = new LinkedHashMap<String, String>();
    200
    201         // Prepared anchor and tag.
    202         this.preparedAnchor = null;
    203         this.preparedTag = null;
    204
    205         // Scalar analysis and style.
    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     // In some cases, we wait for a few next events before emitting.
    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     // States
    272
    273     // Stream handlers.
    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     // Document handlers.
    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                 // TODO fix 313 PyYAML changeset
    341                 // if (openEnded) {
    342                 // writeIndicator("...", true, false, false);
    343                 // writeIndent();
    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     // Node handlers.
    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 {// MappingStartEvent
    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     // Flow sequence handlers.
    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     // Flow mapping handlers.
    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     // Block sequence handlers.
    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     // Block mapping handlers.
    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     // Checkers.
    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     // Anchor, Tag, and Scalar processors.
    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     // Analyzers.
    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         // Empty scalar is a special case.
    901         if (scalar.length() == 0) {
    902             return new ScalarAnalysis(scalar, true, false, false, true, true, true, false);
    903         }
    904         // Indicators and special characters.
    905         boolean blockIndicators = false;
    906         boolean flowIndicators = false;
    907         boolean lineBreaks = false;
    908         boolean specialCharacters = false;
    909
    910         // Important whitespace combinations.
    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         // Check document indicators.
    919         if (scalar.startsWith("---") || scalar.startsWith("...")) {
    920             blockIndicators = true;
    921             flowIndicators = true;
    922         }
    923         // First character or preceded by a whitespace.
    924         boolean preceededByWhitespace = true;
    925         boolean followedByWhitespace = (scalar.length() == 1 || Constant.NULL_BL_T_LINEBR
    926                 .has(scalar.charAt(1)));
    927         // The previous character is a space.
    928         boolean previousSpace = false;
    929
    930         // The previous character is a break.
    931         boolean previousBreak = false;
    932
    933         int index = 0;
    934
    935         while (index < scalar.length()) {
    936             char ch = scalar.charAt(index);
    937             // Check for indicators.
    938             if (index == 0) {
    939                 // Leading indicators are special characters.
    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                 // Some indicators cannot appear within a scalar as well.
    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             // Check for line breaks, special, and unicode characters.
    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                     // unicode is used
    979                     if (!this.allowUnicode) {
    980                         specialCharacters = true;
    981                     }
    982                 } else {
    983                     specialCharacters = true;
    984                 }
    985             }
    986             // Detect important whitespace combinations.
    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             // Prepare for the next character.
    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         // Let's decide what styles are allowed.
    1023         boolean allowFlowPlain = true;
    1024         boolean allowBlockPlain = true;
    1025         boolean allowSingleQuoted = true;
    1026         boolean allowDoubleQuoted = true;
    1027         boolean allowBlock = true;
    1028         // Leading and trailing whitespaces are bad for plain scalars.
    1029         if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) {
    1030             allowFlowPlain = allowBlockPlain = false;
    1031         }
    1032         // We do not permit trailing spaces for block scalars.
    1033         if (trailingSpace) {
    1034             allowBlock = false;
    1035         }
    1036         // Spaces at the beginning of a new line are only acceptable for block
    1037         // scalars.
    1038         if (breakSpace) {
    1039             allowFlowPlain = allowBlockPlain = allowSingleQuoted = false;
    1040         }
    1041         // Spaces followed by breaks, as well as special character are only
    1042         // allowed for double quoted scalars.
    1043         if (spaceBreak || specialCharacters) {
    1044             allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false;
    1045         }
    1046         // Although the plain scalar writer supports breaks, we never emit
    1047         // multiline plain scalars.
    1048         if (lineBreaks) {
    1049             allowFlowPlain = allowBlockPlain = false;
    1050         }
    1051         // Flow indicators are forbidden for flow plain scalars.
    1052         if (flowIndicators) {
    1053             allowFlowPlain = false;
    1054         }
    1055         // Block indicators are forbidden for block plain scalars.
    1056         if (blockIndicators) {
    1057             allowBlockPlain = false;
    1058         }
    1059
    1060         return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain,
    1061                 allowSingleQuoted, allowDoubleQuoted, allowBlock);
    1062     }
    1063
    1064     // Writers.
    1065
    1066     void flushStream() throws IOException {
    1067         stream.flush();
    1068     }
    1069
    1070     void writeStreamStart() {
    1071         // BOM is written by Writer.
    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         // XXX: not sure 4 invocations better then StringBuilders created by str
    1133         // + str
    1134         stream.write("%TAG ");
    1135         stream.write(handleText);
    1136         stream.write(SPACE);
    1137         stream.write(prefixText);
    1138         writeLineBreak(null);
    1139     }
    1140
    1141     // Scalar streams.
    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                         // this is different from PyYAML which escapes all
    1228                         // non-ASCII characters
    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 }