All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yaml.snakeyaml.emitter.Emitter Maven / Gradle / Ivy

There is a newer version: 1.0.366
Show newest version
/**
 * Copyright (c) 2008, SnakeYAML
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.yaml.snakeyaml.emitter;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.ScalarStyle;
import org.yaml.snakeyaml.DumperOptions.Version;
import org.yaml.snakeyaml.comments.CommentEventsCollector;
import org.yaml.snakeyaml.comments.CommentLine;
import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.events.AliasEvent;
import org.yaml.snakeyaml.events.CollectionEndEvent;
import org.yaml.snakeyaml.events.CollectionStartEvent;
import org.yaml.snakeyaml.events.CommentEvent;
import org.yaml.snakeyaml.events.DocumentEndEvent;
import org.yaml.snakeyaml.events.DocumentStartEvent;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.events.Event.ID;
import org.yaml.snakeyaml.events.MappingEndEvent;
import org.yaml.snakeyaml.events.MappingStartEvent;
import org.yaml.snakeyaml.events.NodeEvent;
import org.yaml.snakeyaml.events.ScalarEvent;
import org.yaml.snakeyaml.events.SequenceEndEvent;
import org.yaml.snakeyaml.events.SequenceStartEvent;
import org.yaml.snakeyaml.events.StreamEndEvent;
import org.yaml.snakeyaml.events.StreamStartEvent;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.reader.StreamReader;
import org.yaml.snakeyaml.scanner.Constant;
import org.yaml.snakeyaml.util.ArrayStack;

/**
 * 
 * Emitter expects events obeying the following grammar:
 * stream ::= STREAM-START document* STREAM-END
 * document ::= DOCUMENT-START node DOCUMENT-END
 * node ::= SCALAR | sequence | mapping
 * sequence ::= SEQUENCE-START node* SEQUENCE-END
 * mapping ::= MAPPING-START (node node)* MAPPING-END
 * 
*/ public final class Emitter implements Emitable { /** * indent cannot be zero spaces */ public static final int MIN_INDENT = 1; /** * indent should not be more than 10 spaces */ public static final int MAX_INDENT = 10; private static final char[] SPACE = {' '}; private static final Pattern SPACES_PATTERN = Pattern.compile("\\s"); private static final Set INVALID_ANCHOR = new HashSet(); static { INVALID_ANCHOR.add('['); INVALID_ANCHOR.add(']'); INVALID_ANCHOR.add('{'); INVALID_ANCHOR.add('}'); INVALID_ANCHOR.add(','); INVALID_ANCHOR.add('*'); INVALID_ANCHOR.add('&'); } private static final Map ESCAPE_REPLACEMENTS = new HashMap(); static { ESCAPE_REPLACEMENTS.put('\0', "0"); ESCAPE_REPLACEMENTS.put('\u0007', "a"); ESCAPE_REPLACEMENTS.put('\u0008', "b"); ESCAPE_REPLACEMENTS.put('\u0009', "t"); ESCAPE_REPLACEMENTS.put('\n', "n"); ESCAPE_REPLACEMENTS.put('\u000B', "v"); ESCAPE_REPLACEMENTS.put('\u000C', "f"); ESCAPE_REPLACEMENTS.put('\r', "r"); ESCAPE_REPLACEMENTS.put('\u001B', "e"); ESCAPE_REPLACEMENTS.put('"', "\""); ESCAPE_REPLACEMENTS.put('\\', "\\"); ESCAPE_REPLACEMENTS.put('\u0085', "N"); ESCAPE_REPLACEMENTS.put('\u00A0', "_"); ESCAPE_REPLACEMENTS.put('\u2028', "L"); ESCAPE_REPLACEMENTS.put('\u2029', "P"); } private static final Map DEFAULT_TAG_PREFIXES = new LinkedHashMap(); static { DEFAULT_TAG_PREFIXES.put("!", "!"); DEFAULT_TAG_PREFIXES.put(Tag.PREFIX, "!!"); } // The stream should have the methods `write` and possibly `flush`. private final Writer stream; // Encoding is defined by Writer (cannot be overridden by STREAM-START.) // private Charset encoding; // Emitter is a state machine with a stack of states to handle nested // structures. private final ArrayStack states; private EmitterState state; // Current event and the event queue. private final Queue events; private Event event; // The current indentation level and the stack of previous indents. private final ArrayStack indents; private Integer indent; // Flow level. private int flowLevel; // Contexts. private boolean rootContext; private boolean mappingContext; private boolean simpleKeyContext; // // Characteristics of the last emitted character: // - current position. // - is it a whitespace? // - is it an indention character // (indentation space, '-', '?', or ':')? // private int line; this variable is not used private int column; private boolean whitespace; private boolean indention; private boolean openEnded; // Formatting details. private final Boolean canonical; // pretty print flow by adding extra line breaks private final Boolean prettyFlow; private final boolean allowUnicode; private int bestIndent; private final int indicatorIndent; private final boolean indentWithIndicator; private int bestWidth; private final char[] bestLineBreak; private final boolean splitLines; private final int maxSimpleKeyLength; private final boolean emitComments; // Tag prefixes. private Map tagPrefixes; // Prepared anchor and tag. private String preparedAnchor; private String preparedTag; // Scalar analysis and style. private ScalarAnalysis analysis; private DumperOptions.ScalarStyle style; // Comment processing private final CommentEventsCollector blockCommentsCollector; private final CommentEventsCollector inlineCommentsCollector; /** * Create * * @param stream - output to write to * @param opts - options */ public Emitter(Writer stream, DumperOptions opts) { if (stream == null) { throw new NullPointerException("Writer must be provided."); } if (opts == null) { throw new NullPointerException("DumperOptions must be provided."); } // The stream should have the methods `write` and possibly `flush`. this.stream = stream; // Emitter is a state machine with a stack of states to handle nested structures. this.states = new ArrayStack(100); this.state = new ExpectStreamStart(); // Current event and the event queue. this.events = new ArrayDeque<>(100); this.event = null; // The current indentation level and the stack of previous indents. this.indents = new ArrayStack(10); this.indent = null; // Flow level. this.flowLevel = 0; // Contexts. mappingContext = false; simpleKeyContext = false; // // Characteristics of the last emitted character: // - current position. // - is it a whitespace? // - is it an indention character // (indentation space, '-', '?', or ':')? column = 0; whitespace = true; indention = true; // Whether the document requires an explicit document indicator openEnded = false; // Formatting details. this.canonical = opts.isCanonical(); this.prettyFlow = opts.isPrettyFlow(); this.allowUnicode = opts.isAllowUnicode(); this.bestIndent = 2; if ((opts.getIndent() > MIN_INDENT) && (opts.getIndent() < MAX_INDENT)) { this.bestIndent = opts.getIndent(); } this.indicatorIndent = opts.getIndicatorIndent(); this.indentWithIndicator = opts.getIndentWithIndicator(); this.bestWidth = 80; if (opts.getWidth() > this.bestIndent * 2) { this.bestWidth = opts.getWidth(); } this.bestLineBreak = opts.getLineBreak().getString().toCharArray(); this.splitLines = opts.getSplitLines(); this.maxSimpleKeyLength = opts.getMaxSimpleKeyLength(); this.emitComments = opts.isProcessComments(); // Tag prefixes. this.tagPrefixes = new LinkedHashMap(); // Prepared anchor and tag. this.preparedAnchor = null; this.preparedTag = null; // Scalar analysis and style. this.analysis = null; this.style = null; // Comment processing this.blockCommentsCollector = new CommentEventsCollector(events, CommentType.BLANK_LINE, CommentType.BLOCK); this.inlineCommentsCollector = new CommentEventsCollector(events, CommentType.IN_LINE); } public void emit(Event event) throws IOException { this.events.add(event); while (!needMoreEvents()) { this.event = this.events.poll(); this.state.expect(); this.event = null; } } // In some cases, we wait for a few next events before emitting. private boolean needMoreEvents() { if (events.isEmpty()) { return true; } Iterator iter = events.iterator(); Event event = iter.next(); // it cannot be empty here while (event instanceof CommentEvent) { if (!iter.hasNext()) { return true; } event = iter.next(); } if (event instanceof DocumentStartEvent) { return needEvents(iter, 1); } else if (event instanceof SequenceStartEvent) { return needEvents(iter, 2); } else if (event instanceof MappingStartEvent) { return needEvents(iter, 3); } else if (event instanceof StreamStartEvent) { return needEvents(iter, 2); } else if (event instanceof StreamEndEvent) { return false; } else if (emitComments) { return needEvents(iter, 1); } return false; } private boolean needEvents(Iterator iter, int count) { int level = 0; int actualCount = 0; while (iter.hasNext()) { Event event = iter.next(); if (event instanceof CommentEvent) { continue; } actualCount++; if (event instanceof DocumentStartEvent || event instanceof CollectionStartEvent) { level++; } else if (event instanceof DocumentEndEvent || event instanceof CollectionEndEvent) { level--; } else if (event instanceof StreamEndEvent) { level = -1; } if (level < 0) { return false; } } return actualCount < count; } private void increaseIndent(boolean flow, boolean indentless) { indents.push(indent); if (indent == null) { if (flow) { indent = bestIndent; } else { indent = 0; } } else if (!indentless) { this.indent += bestIndent; } } // States // Stream handlers. private class ExpectStreamStart implements EmitterState { public void expect() throws IOException { if (event instanceof StreamStartEvent) { writeStreamStart(); state = new ExpectFirstDocumentStart(); } else { throw new EmitterException("expected StreamStartEvent, but got " + event); } } } private class ExpectNothing implements EmitterState { public void expect() throws IOException { throw new EmitterException("expecting nothing, but got " + event); } } // Document handlers. private class ExpectFirstDocumentStart implements EmitterState { public void expect() throws IOException { new ExpectDocumentStart(true).expect(); } } private class ExpectDocumentStart implements EmitterState { private final boolean first; public ExpectDocumentStart(boolean first) { this.first = first; } public void expect() throws IOException { if (event instanceof DocumentStartEvent) { DocumentStartEvent ev = (DocumentStartEvent) event; if ((ev.getVersion() != null || ev.getTags() != null) && openEnded) { writeIndicator("...", true, false, false); writeIndent(); } if (ev.getVersion() != null) { String versionText = prepareVersion(ev.getVersion()); writeVersionDirective(versionText); } tagPrefixes = new LinkedHashMap(DEFAULT_TAG_PREFIXES); if (ev.getTags() != null) { Set handles = new TreeSet(ev.getTags().keySet()); for (String handle : handles) { String prefix = ev.getTags().get(handle); tagPrefixes.put(prefix, handle); String handleText = prepareTagHandle(handle); String prefixText = prepareTagPrefix(prefix); writeTagDirective(handleText, prefixText); } } boolean implicit = first && !ev.getExplicit() && !canonical && ev.getVersion() == null && (ev.getTags() == null || ev.getTags().isEmpty()) && !checkEmptyDocument(); if (!implicit) { writeIndent(); writeIndicator("---", true, false, false); if (canonical) { writeIndent(); } } state = new ExpectDocumentRoot(); } else if (event instanceof StreamEndEvent) { writeStreamEnd(); state = new ExpectNothing(); } else if (event instanceof CommentEvent) { blockCommentsCollector.collectEvents(event); writeBlockComment(); // state = state; remains unchanged } else { throw new EmitterException("expected DocumentStartEvent, but got " + event); } } } private class ExpectDocumentEnd implements EmitterState { public void expect() throws IOException { event = blockCommentsCollector.collectEventsAndPoll(event); writeBlockComment(); if (event instanceof DocumentEndEvent) { writeIndent(); if (((DocumentEndEvent) event).getExplicit()) { writeIndicator("...", true, false, false); writeIndent(); } flushStream(); state = new ExpectDocumentStart(false); } else { throw new EmitterException("expected DocumentEndEvent, but got " + event); } } } private class ExpectDocumentRoot implements EmitterState { public void expect() throws IOException { event = blockCommentsCollector.collectEventsAndPoll(event); if (!blockCommentsCollector.isEmpty()) { writeBlockComment(); if (event instanceof DocumentEndEvent) { new ExpectDocumentEnd().expect(); return; } } states.push(new ExpectDocumentEnd()); expectNode(true, false, false); } } // Node handlers. private void expectNode(boolean root, boolean mapping, boolean simpleKey) throws IOException { rootContext = root; mappingContext = mapping; simpleKeyContext = simpleKey; if (event instanceof AliasEvent) { expectAlias(); } else if (event instanceof ScalarEvent || event instanceof CollectionStartEvent) { processAnchor("&"); processTag(); if (event instanceof ScalarEvent) { expectScalar(); } else if (event instanceof SequenceStartEvent) { if (flowLevel != 0 || canonical || ((SequenceStartEvent) event).isFlow() || checkEmptySequence()) { expectFlowSequence(); } else { expectBlockSequence(); } } else {// MappingStartEvent if (flowLevel != 0 || canonical || ((MappingStartEvent) event).isFlow() || checkEmptyMapping()) { expectFlowMapping(); } else { expectBlockMapping(); } } } else { throw new EmitterException("expected NodeEvent, but got " + event); } } private void expectAlias() throws IOException { if (!(event instanceof AliasEvent)) { throw new EmitterException("Alias must be provided"); } processAnchor("*"); state = states.pop(); } private void expectScalar() throws IOException { increaseIndent(true, false); processScalar(); indent = indents.pop(); state = states.pop(); } // Flow sequence handlers. private void expectFlowSequence() throws IOException { writeIndicator("[", true, true, false); flowLevel++; increaseIndent(true, false); if (prettyFlow) { writeIndent(); } state = new ExpectFirstFlowSequenceItem(); } private class ExpectFirstFlowSequenceItem implements EmitterState { public void expect() throws IOException { if (event instanceof SequenceEndEvent) { indent = indents.pop(); flowLevel--; writeIndicator("]", false, false, false); inlineCommentsCollector.collectEvents(); writeInlineComments(); state = states.pop(); } else if (event instanceof CommentEvent) { blockCommentsCollector.collectEvents(event); writeBlockComment(); } else { if (canonical || (column > bestWidth && splitLines) || prettyFlow) { writeIndent(); } states.push(new ExpectFlowSequenceItem()); expectNode(false, false, false); event = inlineCommentsCollector.collectEvents(event); writeInlineComments(); } } } private class ExpectFlowSequenceItem implements EmitterState { public void expect() throws IOException { if (event instanceof SequenceEndEvent) { indent = indents.pop(); flowLevel--; if (canonical) { writeIndicator(",", false, false, false); writeIndent(); } else if (prettyFlow) { writeIndent(); } writeIndicator("]", false, false, false); inlineCommentsCollector.collectEvents(); writeInlineComments(); if (prettyFlow) { writeIndent(); } state = states.pop(); } else if (event instanceof CommentEvent) { event = blockCommentsCollector.collectEvents(event); } else { writeIndicator(",", false, false, false); writeBlockComment(); if (canonical || (column > bestWidth && splitLines) || prettyFlow) { writeIndent(); } states.push(new ExpectFlowSequenceItem()); expectNode(false, false, false); event = inlineCommentsCollector.collectEvents(event); writeInlineComments(); } } } // Flow mapping handlers. private void expectFlowMapping() throws IOException { writeIndicator("{", true, true, false); flowLevel++; increaseIndent(true, false); if (prettyFlow) { writeIndent(); } state = new ExpectFirstFlowMappingKey(); } private class ExpectFirstFlowMappingKey implements EmitterState { public void expect() throws IOException { event = blockCommentsCollector.collectEventsAndPoll(event); writeBlockComment(); if (event instanceof MappingEndEvent) { indent = indents.pop(); flowLevel--; writeIndicator("}", false, false, false); inlineCommentsCollector.collectEvents(); writeInlineComments(); state = states.pop(); } else { if (canonical || (column > bestWidth && splitLines) || prettyFlow) { writeIndent(); } if (!canonical && checkSimpleKey()) { states.push(new ExpectFlowMappingSimpleValue()); expectNode(false, true, true); } else { writeIndicator("?", true, false, false); states.push(new ExpectFlowMappingValue()); expectNode(false, true, false); } } } } private class ExpectFlowMappingKey implements EmitterState { public void expect() throws IOException { if (event instanceof MappingEndEvent) { indent = indents.pop(); flowLevel--; if (canonical) { writeIndicator(",", false, false, false); writeIndent(); } if (prettyFlow) { writeIndent(); } writeIndicator("}", false, false, false); inlineCommentsCollector.collectEvents(); writeInlineComments(); state = states.pop(); } else { writeIndicator(",", false, false, false); event = blockCommentsCollector.collectEventsAndPoll(event); writeBlockComment(); if (canonical || (column > bestWidth && splitLines) || prettyFlow) { writeIndent(); } if (!canonical && checkSimpleKey()) { states.push(new ExpectFlowMappingSimpleValue()); expectNode(false, true, true); } else { writeIndicator("?", true, false, false); states.push(new ExpectFlowMappingValue()); expectNode(false, true, false); } } } } private class ExpectFlowMappingSimpleValue implements EmitterState { public void expect() throws IOException { writeIndicator(":", false, false, false); event = inlineCommentsCollector.collectEventsAndPoll(event); writeInlineComments(); states.push(new ExpectFlowMappingKey()); expectNode(false, true, false); inlineCommentsCollector.collectEvents(event); writeInlineComments(); } } private class ExpectFlowMappingValue implements EmitterState { public void expect() throws IOException { if (canonical || (column > bestWidth) || prettyFlow) { writeIndent(); } writeIndicator(":", true, false, false); event = inlineCommentsCollector.collectEventsAndPoll(event); writeInlineComments(); states.push(new ExpectFlowMappingKey()); expectNode(false, true, false); inlineCommentsCollector.collectEvents(event); writeInlineComments(); } } // Block sequence handlers. private void expectBlockSequence() throws IOException { boolean indentless = mappingContext && !indention; increaseIndent(false, indentless); state = new ExpectFirstBlockSequenceItem(); } private class ExpectFirstBlockSequenceItem implements EmitterState { public void expect() throws IOException { new ExpectBlockSequenceItem(true).expect(); } } private class ExpectBlockSequenceItem implements EmitterState { private final boolean first; public ExpectBlockSequenceItem(boolean first) { this.first = first; } public void expect() throws IOException { if (!this.first && event instanceof SequenceEndEvent) { indent = indents.pop(); state = states.pop(); } else if (event instanceof CommentEvent) { blockCommentsCollector.collectEvents(event); } else { writeIndent(); if (!indentWithIndicator || this.first) { writeWhitespace(indicatorIndent); } writeIndicator("-", true, false, true); if (indentWithIndicator && this.first) { indent += indicatorIndent; } if (!blockCommentsCollector.isEmpty()) { increaseIndent(false, false); writeBlockComment(); if (event instanceof ScalarEvent) { analysis = analyzeScalar(((ScalarEvent) event).getValue()); if (!analysis.isEmpty()) { writeIndent(); } } indent = indents.pop(); } states.push(new ExpectBlockSequenceItem(false)); expectNode(false, false, false); inlineCommentsCollector.collectEvents(); writeInlineComments(); } } } // Block mapping handlers. private void expectBlockMapping() throws IOException { increaseIndent(false, false); state = new ExpectFirstBlockMappingKey(); } private class ExpectFirstBlockMappingKey implements EmitterState { public void expect() throws IOException { new ExpectBlockMappingKey(true).expect(); } } private class ExpectBlockMappingKey implements EmitterState { private final boolean first; public ExpectBlockMappingKey(boolean first) { this.first = first; } public void expect() throws IOException { event = blockCommentsCollector.collectEventsAndPoll(event); writeBlockComment(); if (!this.first && event instanceof MappingEndEvent) { indent = indents.pop(); state = states.pop(); } else { writeIndent(); if (checkSimpleKey()) { states.push(new ExpectBlockMappingSimpleValue()); expectNode(false, true, true); } else { writeIndicator("?", true, false, true); states.push(new ExpectBlockMappingValue()); expectNode(false, true, false); } } } } private boolean isFoldedOrLiteral(Event event) { if (!event.is(ID.Scalar)) { return false; } ScalarEvent scalarEvent = (ScalarEvent) event; ScalarStyle style = scalarEvent.getScalarStyle(); return style == ScalarStyle.FOLDED || style == ScalarStyle.LITERAL; } private class ExpectBlockMappingSimpleValue implements EmitterState { public void expect() throws IOException { writeIndicator(":", false, false, false); event = inlineCommentsCollector.collectEventsAndPoll(event); if (!isFoldedOrLiteral(event)) { if (writeInlineComments()) { increaseIndent(true, false); writeIndent(); indent = indents.pop(); } } event = blockCommentsCollector.collectEventsAndPoll(event); if (!blockCommentsCollector.isEmpty()) { increaseIndent(true, false); writeBlockComment(); writeIndent(); indent = indents.pop(); } states.push(new ExpectBlockMappingKey(false)); expectNode(false, true, false); inlineCommentsCollector.collectEvents(); writeInlineComments(); } } private class ExpectBlockMappingValue implements EmitterState { public void expect() throws IOException { writeIndent(); writeIndicator(":", true, false, true); event = inlineCommentsCollector.collectEventsAndPoll(event); writeInlineComments(); event = blockCommentsCollector.collectEventsAndPoll(event); writeBlockComment(); states.push(new ExpectBlockMappingKey(false)); expectNode(false, true, false); inlineCommentsCollector.collectEvents(event); writeInlineComments(); } } // Checkers. private boolean checkEmptySequence() { return event instanceof SequenceStartEvent && !events.isEmpty() && events.peek() instanceof SequenceEndEvent; } private boolean checkEmptyMapping() { return event instanceof MappingStartEvent && !events.isEmpty() && events.peek() instanceof MappingEndEvent; } private boolean checkEmptyDocument() { if (!(event instanceof DocumentStartEvent) || events.isEmpty()) { return false; } Event event = events.peek(); if (event instanceof ScalarEvent) { ScalarEvent e = (ScalarEvent) event; return e.getAnchor() == null && e.getTag() == null && e.getImplicit() != null && e.getValue().length() == 0; } return false; } private boolean checkSimpleKey() { int length = 0; if (event instanceof NodeEvent && ((NodeEvent) event).getAnchor() != null) { if (preparedAnchor == null) { preparedAnchor = prepareAnchor(((NodeEvent) event).getAnchor()); } length += preparedAnchor.length(); } String tag = null; if (event instanceof ScalarEvent) { tag = ((ScalarEvent) event).getTag(); } else if (event instanceof CollectionStartEvent) { tag = ((CollectionStartEvent) event).getTag(); } if (tag != null) { if (preparedTag == null) { preparedTag = prepareTag(tag); } length += preparedTag.length(); } if (event instanceof ScalarEvent) { if (analysis == null) { analysis = analyzeScalar(((ScalarEvent) event).getValue()); } length += analysis.getScalar().length(); } return length < maxSimpleKeyLength && (event instanceof AliasEvent || (event instanceof ScalarEvent && !analysis.isEmpty() && !analysis.isMultiline()) || checkEmptySequence() || checkEmptyMapping()); } // Anchor, Tag, and Scalar processors. private void processAnchor(String indicator) throws IOException { NodeEvent ev = (NodeEvent) event; if (ev.getAnchor() == null) { preparedAnchor = null; return; } if (preparedAnchor == null) { preparedAnchor = prepareAnchor(ev.getAnchor()); } writeIndicator(indicator + preparedAnchor, true, false, false); preparedAnchor = null; } private void processTag() throws IOException { String tag = null; if (event instanceof ScalarEvent) { ScalarEvent ev = (ScalarEvent) event; tag = ev.getTag(); if (style == null) { style = chooseScalarStyle(); } if ((!canonical || tag == null) && ((style == null && ev.getImplicit().canOmitTagInPlainScalar()) || (style != null && ev.getImplicit().canOmitTagInNonPlainScalar()))) { preparedTag = null; return; } if (ev.getImplicit().canOmitTagInPlainScalar() && tag == null) { tag = "!"; preparedTag = null; } } else { CollectionStartEvent ev = (CollectionStartEvent) event; tag = ev.getTag(); if ((!canonical || tag == null) && ev.getImplicit()) { preparedTag = null; return; } } if (tag == null) { throw new EmitterException("tag is not specified"); } if (preparedTag == null) { preparedTag = prepareTag(tag); } writeIndicator(preparedTag, true, false, false); preparedTag = null; } private DumperOptions.ScalarStyle chooseScalarStyle() { ScalarEvent ev = (ScalarEvent) event; if (analysis == null) { analysis = analyzeScalar(ev.getValue()); } if (!ev.isPlain() && ev.getScalarStyle() == DumperOptions.ScalarStyle.DOUBLE_QUOTED || this.canonical) { return DumperOptions.ScalarStyle.DOUBLE_QUOTED; } if (ev.isPlain() && ev.getImplicit().canOmitTagInPlainScalar()) { if (!(simpleKeyContext && (analysis.isEmpty() || analysis.isMultiline())) && ((flowLevel != 0 && analysis.isAllowFlowPlain()) || (flowLevel == 0 && analysis.isAllowBlockPlain()))) { return null; } } if (!ev.isPlain() && (ev.getScalarStyle() == DumperOptions.ScalarStyle.LITERAL || ev.getScalarStyle() == DumperOptions.ScalarStyle.FOLDED)) { if (flowLevel == 0 && !simpleKeyContext && analysis.isAllowBlock()) { return ev.getScalarStyle(); } } if (ev.isPlain() || ev.getScalarStyle() == DumperOptions.ScalarStyle.SINGLE_QUOTED) { if (analysis.isAllowSingleQuoted() && !(simpleKeyContext && analysis.isMultiline())) { return DumperOptions.ScalarStyle.SINGLE_QUOTED; } } return DumperOptions.ScalarStyle.DOUBLE_QUOTED; } private void processScalar() throws IOException { ScalarEvent ev = (ScalarEvent) event; if (analysis == null) { analysis = analyzeScalar(ev.getValue()); } if (style == null) { style = chooseScalarStyle(); } boolean split = !simpleKeyContext && splitLines; if (style == null) { writePlain(analysis.getScalar(), split); } else { switch (style) { case DOUBLE_QUOTED: writeDoubleQuoted(analysis.getScalar(), split); break; case SINGLE_QUOTED: writeSingleQuoted(analysis.getScalar(), split); break; case FOLDED: writeFolded(analysis.getScalar(), split); break; case LITERAL: writeLiteral(analysis.getScalar()); break; default: throw new YAMLException("Unexpected style: " + style); } } analysis = null; style = null; } // Analyzers. private String prepareVersion(Version version) { if (version.major() != 1) { throw new EmitterException("unsupported YAML version: " + version); } return version.getRepresentation(); } private static final Pattern HANDLE_FORMAT = Pattern.compile("^![-_\\w]*!$"); private String prepareTagHandle(String handle) { if (handle.length() == 0) { throw new EmitterException("tag handle must not be empty"); } else if (handle.charAt(0) != '!' || handle.charAt(handle.length() - 1) != '!') { throw new EmitterException("tag handle must start and end with '!': " + handle); } else if (!"!".equals(handle) && !HANDLE_FORMAT.matcher(handle).matches()) { throw new EmitterException("invalid character in the tag handle: " + handle); } return handle; } private String prepareTagPrefix(String prefix) { if (prefix.length() == 0) { throw new EmitterException("tag prefix must not be empty"); } StringBuilder chunks = new StringBuilder(); int start = 0; int end = 0; if (prefix.charAt(0) == '!') { end = 1; } while (end < prefix.length()) { end++; } if (start < end) { chunks.append(prefix, start, end); } return chunks.toString(); } private String prepareTag(String tag) { if (tag.length() == 0) { throw new EmitterException("tag must not be empty"); } if ("!".equals(tag)) { return tag; } String handle = null; String suffix = tag; // shall the tag prefixes be sorted as in PyYAML? for (String prefix : tagPrefixes.keySet()) { if (tag.startsWith(prefix) && ("!".equals(prefix) || prefix.length() < tag.length())) { handle = prefix; } } if (handle != null) { suffix = tag.substring(handle.length()); handle = tagPrefixes.get(handle); } int end = suffix.length(); String suffixText = end > 0 ? suffix.substring(0, end) : ""; if (handle != null) { return handle + suffixText; } return "!<" + suffixText + ">"; } static String prepareAnchor(String anchor) { if (anchor.length() == 0) { throw new EmitterException("anchor must not be empty"); } for (Character invalid : INVALID_ANCHOR) { if (anchor.indexOf(invalid) > -1) { throw new EmitterException("Invalid character '" + invalid + "' in the anchor: " + anchor); } } Matcher matcher = SPACES_PATTERN.matcher(anchor); if (matcher.find()) { throw new EmitterException("Anchor may not contain spaces: " + anchor); } return anchor; } // Equivalent to Pattern.compile("0[0-9_]+").matcher(scalar).matches(). private static boolean hasLeadingZero(String scalar) { if (scalar.length() > 1 && scalar.charAt(0) == '0') { for (int i = 1; i < scalar.length(); i++) { char ch = scalar.charAt(i); boolean isDigitOrUnderscore = (ch >= '0' && ch <= '9') || ch == '_'; if (!isDigitOrUnderscore) { return false; } } return true; } return false; } private ScalarAnalysis analyzeScalar(String scalar) { // Empty scalar is a special case. if (scalar.length() == 0) { return new ScalarAnalysis(scalar, true, false, false, true, true, false); } // Indicators and special characters. boolean blockIndicators = false; boolean flowIndicators = false; boolean lineBreaks = false; boolean specialCharacters = false; boolean leadingZeroNumber = hasLeadingZero(scalar); // Important whitespace combinations. boolean leadingSpace = false; boolean leadingBreak = false; boolean trailingSpace = false; boolean trailingBreak = false; boolean breakSpace = false; boolean spaceBreak = false; // Check document indicators. if (scalar.startsWith("---") || scalar.startsWith("...")) { blockIndicators = true; flowIndicators = true; } // First character or preceded by a whitespace. boolean preceededByWhitespace = true; boolean followedByWhitespace = scalar.length() == 1 || Constant.NULL_BL_T_LINEBR.has(scalar.codePointAt(1)); // The previous character is a space. boolean previousSpace = false; // The previous character is a break. boolean previousBreak = false; int index = 0; while (index < scalar.length()) { int c = scalar.codePointAt(index); // Check for indicators. if (index == 0) { // Leading indicators are special characters. if ("#,[]{}&*!|>'\"%@`".indexOf(c) != -1) { flowIndicators = true; blockIndicators = true; } if (c == '?' || c == ':') { flowIndicators = true; if (followedByWhitespace) { blockIndicators = true; } } if (c == '-' && followedByWhitespace) { flowIndicators = true; blockIndicators = true; } } else { // Some indicators cannot appear within a scalar as well. if (",?[]{}".indexOf(c) != -1) { flowIndicators = true; } if (c == ':') { flowIndicators = true; if (followedByWhitespace) { blockIndicators = true; } } if (c == '#' && preceededByWhitespace) { flowIndicators = true; blockIndicators = true; } } // Check for line breaks, special, and unicode characters. boolean isLineBreak = Constant.LINEBR.has(c); if (isLineBreak) { lineBreaks = true; } if (!(c == '\n' || (0x20 <= c && c <= 0x7E))) { if (c == 0x85 || (c >= 0xA0 && c <= 0xD7FF) || (c >= 0xE000 && c <= 0xFFFD) || (c >= 0x10000 && c <= 0x10FFFF)) { // unicode is used if (!this.allowUnicode) { specialCharacters = true; } } else { specialCharacters = true; } } // Detect important whitespace combinations. if (c == ' ') { if (index == 0) { leadingSpace = true; } if (index == scalar.length() - 1) { trailingSpace = true; } if (previousBreak) { breakSpace = true; } previousSpace = true; previousBreak = false; } else if (isLineBreak) { if (index == 0) { leadingBreak = true; } if (index == scalar.length() - 1) { trailingBreak = true; } if (previousSpace) { spaceBreak = true; } previousSpace = false; previousBreak = true; } else { previousSpace = false; previousBreak = false; } // Prepare for the next character. index += Character.charCount(c); preceededByWhitespace = Constant.NULL_BL_T.has(c) || isLineBreak; followedByWhitespace = true; if (index + 1 < scalar.length()) { int nextIndex = index + Character.charCount(scalar.codePointAt(index)); if (nextIndex < scalar.length()) { followedByWhitespace = (Constant.NULL_BL_T.has(scalar.codePointAt(nextIndex))) || isLineBreak; } } } // Let's decide what styles are allowed. boolean allowFlowPlain = true; boolean allowBlockPlain = true; boolean allowSingleQuoted = true; boolean allowBlock = true; // Leading and trailing whitespaces are bad for plain scalars. if (leadingSpace || leadingBreak || trailingSpace || trailingBreak || leadingZeroNumber) { allowFlowPlain = allowBlockPlain = false; } // We do not permit trailing spaces for block scalars. if (trailingSpace) { allowBlock = false; } // Spaces at the beginning of a new line are only acceptable for block // scalars. if (breakSpace) { allowFlowPlain = allowBlockPlain = allowSingleQuoted = false; } // Spaces followed by breaks, as well as special character are only // allowed for double-quoted scalars. if (spaceBreak || specialCharacters) { allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false; } // Although the plain scalar writer supports breaks, we never emit // multiline plain scalars in the flow context. if (lineBreaks) { allowFlowPlain = false; } // Flow indicators are forbidden for flow plain scalars. if (flowIndicators) { allowFlowPlain = false; } // Block indicators are forbidden for block plain scalars. if (blockIndicators) { allowBlockPlain = false; } return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain, allowSingleQuoted, allowBlock); } // Writers. void flushStream() throws IOException { stream.flush(); } void writeStreamStart() { // BOM is written by Writer. } void writeStreamEnd() throws IOException { flushStream(); } void writeIndicator(String indicator, boolean needWhitespace, boolean whitespace, boolean indentation) throws IOException { if (!this.whitespace && needWhitespace) { this.column++; stream.write(SPACE); } this.whitespace = whitespace; this.indention = this.indention && indentation; this.column += indicator.length(); openEnded = false; stream.write(indicator); } void writeIndent() throws IOException { int indent; if (this.indent != null) { indent = this.indent; } else { indent = 0; } if (!this.indention || this.column > indent || (this.column == indent && !this.whitespace)) { writeLineBreak(null); } writeWhitespace(indent - this.column); } private void writeWhitespace(int length) throws IOException { if (length <= 0) { return; } this.whitespace = true; char[] data = new char[length]; for (int i = 0; i < data.length; i++) { data[i] = ' '; } this.column += length; stream.write(data); } private void writeLineBreak(String data) throws IOException { this.whitespace = true; this.indention = true; this.column = 0; if (data == null) { stream.write(this.bestLineBreak); } else { stream.write(data); } } void writeVersionDirective(String versionText) throws IOException { stream.write("%YAML "); stream.write(versionText); writeLineBreak(null); } void writeTagDirective(String handleText, String prefixText) throws IOException { // XXX: not sure 4 invocations better then StringBuilders created by str // + str stream.write("%TAG "); stream.write(handleText); stream.write(SPACE); stream.write(prefixText); writeLineBreak(null); } // Scalar streams. private void writeSingleQuoted(String text, boolean split) throws IOException { writeIndicator("'", true, false, false); boolean spaces = false; boolean breaks = false; int start = 0, end = 0; char ch; while (end <= text.length()) { ch = 0; if (end < text.length()) { ch = text.charAt(end); } if (spaces) { if (ch == 0 || ch != ' ') { if (start + 1 == end && this.column > this.bestWidth && split && start != 0 && end != text.length()) { writeIndent(); } else { int len = end - start; this.column += len; stream.write(text, start, len); } start = end; } } else if (breaks) { if (ch == 0 || Constant.LINEBR.hasNo(ch)) { if (text.charAt(start) == '\n') { writeLineBreak(null); } String data = text.substring(start, end); for (char br : data.toCharArray()) { if (br == '\n') { writeLineBreak(null); } else { writeLineBreak(String.valueOf(br)); } } writeIndent(); start = end; } } else { if (Constant.LINEBR.has(ch, "\0 '")) { if (start < end) { int len = end - start; this.column += len; stream.write(text, start, len); start = end; } } } if (ch == '\'') { this.column += 2; stream.write("''"); start = end + 1; } if (ch != 0) { spaces = ch == ' '; breaks = Constant.LINEBR.has(ch); } end++; } writeIndicator("'", false, false, false); } private void writeDoubleQuoted(String text, boolean split) throws IOException { writeIndicator("\"", true, false, false); int start = 0; int end = 0; while (end <= text.length()) { Character ch = null; if (end < text.length()) { ch = text.charAt(end); } if (ch == null || "\"\\\u0085\u2028\u2029\uFEFF".indexOf(ch) != -1 || !('\u0020' <= ch && ch <= '\u007E')) { if (start < end) { int len = end - start; this.column += len; stream.write(text, start, len); start = end; } if (ch != null) { String data; if (ESCAPE_REPLACEMENTS.containsKey(ch)) { data = "\\" + ESCAPE_REPLACEMENTS.get(ch); } else { int codePoint; if (Character.isHighSurrogate(ch) && end + 1 < text.length()) { char ch2 = text.charAt(end + 1); codePoint = Character.toCodePoint(ch, ch2); } else { codePoint = ch; } if (this.allowUnicode && StreamReader.isPrintable(codePoint)) { data = String.valueOf(Character.toChars(codePoint)); if (Character.charCount(codePoint) == 2) { end++; } } else { // if !allowUnicode or the character is not printable, // we must encode it if (ch <= '\u00FF') { String s = "0" + Integer.toString(ch, 16); data = "\\x" + s.substring(s.length() - 2); } else if (Character.charCount(codePoint) == 2) { end++; String s = "000" + Long.toHexString(codePoint); data = "\\U" + s.substring(s.length() - 8); } else { String s = "000" + Integer.toString(ch, 16); data = "\\u" + s.substring(s.length() - 4); } } } this.column += data.length(); stream.write(data); start = end + 1; } } if ((0 < end && end < (text.length() - 1)) && (ch == ' ' || start >= end) && (this.column + (end - start)) > this.bestWidth && split) { String data; if (start >= end) { data = "\\"; } else { data = text.substring(start, end) + "\\"; } if (start < end) { start = end; } this.column += data.length(); stream.write(data); writeIndent(); this.whitespace = false; this.indention = false; if (text.charAt(start) == ' ') { data = "\\"; this.column += data.length(); stream.write(data); } } end += 1; } writeIndicator("\"", false, false, false); } private boolean writeCommentLines(List commentLines) throws IOException { boolean wroteComment = false; if (emitComments) { int indentColumns = 0; boolean firstComment = true; for (CommentLine commentLine : commentLines) { if (commentLine.getCommentType() != CommentType.BLANK_LINE) { if (firstComment) { firstComment = false; writeIndicator("#", commentLine.getCommentType() == CommentType.IN_LINE, false, false); indentColumns = this.column > 0 ? this.column - 1 : 0; } else { writeWhitespace(indentColumns); writeIndicator("#", false, false, false); } stream.write(commentLine.getValue()); writeLineBreak(null); } else { writeLineBreak(null); writeIndent(); } wroteComment = true; } } return wroteComment; } private void writeBlockComment() throws IOException { if (!blockCommentsCollector.isEmpty()) { writeIndent(); writeCommentLines(blockCommentsCollector.consume()); } } private boolean writeInlineComments() throws IOException { return writeCommentLines(inlineCommentsCollector.consume()); } private String determineBlockHints(String text) { StringBuilder hints = new StringBuilder(); if (Constant.LINEBR.has(text.charAt(0), " ")) { hints.append(bestIndent); } char ch1 = text.charAt(text.length() - 1); if (Constant.LINEBR.hasNo(ch1)) { hints.append("-"); } else if (text.length() == 1 || Constant.LINEBR.has(text.charAt(text.length() - 2))) { hints.append("+"); } return hints.toString(); } void writeFolded(String text, boolean split) throws IOException { String hints = determineBlockHints(text); writeIndicator(">" + hints, true, false, false); if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) { openEnded = true; } if (!writeInlineComments()) { writeLineBreak(null); } boolean leadingSpace = true; boolean spaces = false; boolean breaks = true; int start = 0, end = 0; while (end <= text.length()) { char ch = 0; if (end < text.length()) { ch = text.charAt(end); } if (breaks) { if (ch == 0 || Constant.LINEBR.hasNo(ch)) { if (!leadingSpace && ch != 0 && ch != ' ' && text.charAt(start) == '\n') { writeLineBreak(null); } leadingSpace = ch == ' '; String data = text.substring(start, end); for (char br : data.toCharArray()) { if (br == '\n') { writeLineBreak(null); } else { writeLineBreak(String.valueOf(br)); } } if (ch != 0) { writeIndent(); } start = end; } } else if (spaces) { if (ch != ' ') { if (start + 1 == end && this.column > this.bestWidth && split) { writeIndent(); } else { int len = end - start; this.column += len; stream.write(text, start, len); } start = end; } } else { if (Constant.LINEBR.has(ch, "\0 ")) { int len = end - start; this.column += len; stream.write(text, start, len); if (ch == 0) { writeLineBreak(null); } start = end; } } if (ch != 0) { breaks = Constant.LINEBR.has(ch); spaces = ch == ' '; } end++; } } void writeLiteral(String text) throws IOException { String hints = determineBlockHints(text); writeIndicator("|" + hints, true, false, false); if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') { openEnded = true; } if (!writeInlineComments()) { writeLineBreak(null); } boolean breaks = true; int start = 0, end = 0; while (end <= text.length()) { char ch = 0; if (end < text.length()) { ch = text.charAt(end); } if (breaks) { if (ch == 0 || Constant.LINEBR.hasNo(ch)) { String data = text.substring(start, end); for (char br : data.toCharArray()) { if (br == '\n') { writeLineBreak(null); } else { writeLineBreak(String.valueOf(br)); } } if (ch != 0) { writeIndent(); } start = end; } } else { if (ch == 0 || Constant.LINEBR.has(ch)) { stream.write(text, start, end - start); if (ch == 0) { writeLineBreak(null); } start = end; } } if (ch != 0) { breaks = Constant.LINEBR.has(ch); } end++; } } void writePlain(String text, boolean split) throws IOException { if (rootContext) { openEnded = true; } if (text.length() == 0) { return; } if (!this.whitespace) { this.column++; stream.write(SPACE); } this.whitespace = false; this.indention = false; boolean spaces = false; boolean breaks = false; int start = 0, end = 0; while (end <= text.length()) { char ch = 0; if (end < text.length()) { ch = text.charAt(end); } if (spaces) { if (ch != ' ') { if (start + 1 == end && this.column > this.bestWidth && split) { writeIndent(); this.whitespace = false; this.indention = false; } else { int len = end - start; this.column += len; stream.write(text, start, len); } start = end; } } else if (breaks) { if (Constant.LINEBR.hasNo(ch)) { if (text.charAt(start) == '\n') { writeLineBreak(null); } String data = text.substring(start, end); for (char br : data.toCharArray()) { if (br == '\n') { writeLineBreak(null); } else { writeLineBreak(String.valueOf(br)); } } writeIndent(); this.whitespace = false; this.indention = false; start = end; } } else { if (Constant.LINEBR.has(ch, "\0 ")) { int len = end - start; this.column += len; stream.write(text, start, len); start = end; } } if (ch != 0) { spaces = ch == ' '; breaks = Constant.LINEBR.has(ch); } end++; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy