/*
* Copyright (c) 2006 Ola Bini
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* $Id: EmitterImpl.java,v 1.5 2006/09/30 14:13:35 olabini Exp $
*/
package org.jvyaml;
import java.io.IOException;
import java.io.Writer;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import org.jvyaml.events.Event;
import org.jvyaml.events.StreamStartEvent;
import org.jvyaml.events.StreamEndEvent;
import org.jvyaml.events.DocumentStartEvent;
import org.jvyaml.events.DocumentEndEvent;
import org.jvyaml.events.CollectionStartEvent;
import org.jvyaml.events.CollectionEndEvent;
import org.jvyaml.events.MappingStartEvent;
import org.jvyaml.events.SequenceStartEvent;
import org.jvyaml.events.MappingEndEvent;
import org.jvyaml.events.SequenceEndEvent;
import org.jvyaml.events.AliasEvent;
import org.jvyaml.events.ScalarEvent;
import org.jvyaml.events.NodeEvent;
/**
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
* @version $Revision: 1.5 $
*/
public class EmitterImpl implements Emitter {
private static class ScalarAnalysis {
public String scalar;
public boolean empty;
public boolean multiline;
public boolean allowFlowPlain;
public boolean allowBlockPlain;
public boolean allowSingleQuoted;
public boolean allowDoubleQuoted;
public boolean allowBlock;
public ScalarAnalysis(final String scalar, final boolean empty, final boolean multiline, final boolean allowFlowPlain, final boolean allowBlockPlain, final boolean allowSingleQuoted, final boolean allowDoubleQuoted, final boolean allowBlock) {
this.scalar = scalar;
this.empty = empty;
this.multiline = multiline;
this.allowFlowPlain = allowFlowPlain;
this.allowBlockPlain = allowBlockPlain;
this.allowSingleQuoted = allowSingleQuoted;
this.allowDoubleQuoted = allowDoubleQuoted;
this.allowBlock = allowBlock;
}
}
private static interface EmitterState {
void expect(final EmitterEnvironment env) throws IOException;
}
private static final int STREAM_START = 0;
private static final int FIRST_DOCUMENT_START = 1;
private static final int DOCUMENT_ROOT = 2;
private static final int NOTHING = 3;
private static final int DOCUMENT_START = 4;
private static final int DOCUMENT_END = 5;
private static final int FIRST_FLOW_SEQUENCE_ITEM = 6;
private static final int FLOW_SEQUENCE_ITEM = 7;
private static final int FIRST_FLOW_MAPPING_KEY = 8;
private static final int FLOW_MAPPING_SIMPLE_VALUE = 9;
private static final int FLOW_MAPPING_VALUE = 10;
private static final int FLOW_MAPPING_KEY = 11;
private static final int BLOCK_SEQUENCE_ITEM = 12;
private static final int FIRST_BLOCK_MAPPING_KEY = 13;
private static final int BLOCK_MAPPING_SIMPLE_VALUE = 14;
private static final int BLOCK_MAPPING_VALUE = 15;
private static final int BLOCK_MAPPING_KEY = 16;
private static final int FIRST_BLOCK_SEQUENCE_ITEM = 17;
private static final EmitterState[] STATES = new EmitterState[18];
static {
STATES[STREAM_START] = new EmitterState() {
public void expect(final EmitterEnvironment env) {
env.expectStreamStart();
}
};
STATES[FIRST_DOCUMENT_START] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectDocumentStart(true);
}
};
STATES[DOCUMENT_ROOT] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectDocumentRoot();
}
};
STATES[NOTHING] = new EmitterState() {
public void expect(final EmitterEnvironment env) {
env.expectNothing();
}
};
STATES[DOCUMENT_START] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectDocumentStart(false);
}
};
STATES[DOCUMENT_END] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectDocumentEnd();
}
};
STATES[FIRST_FLOW_SEQUENCE_ITEM] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectFirstFlowSequenceItem();
}
};
STATES[FLOW_SEQUENCE_ITEM] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectFlowSequenceItem();
}
};
STATES[FIRST_FLOW_MAPPING_KEY] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectFirstFlowMappingKey();
}
};
STATES[FLOW_MAPPING_SIMPLE_VALUE] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectFlowMappingSimpleValue();
}
};
STATES[FLOW_MAPPING_VALUE] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectFlowMappingValue();
}
};
STATES[FLOW_MAPPING_KEY] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectFlowMappingKey();
}
};
STATES[BLOCK_SEQUENCE_ITEM] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectBlockSequenceItem(false);
}
};
STATES[FIRST_BLOCK_MAPPING_KEY] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectFirstBlockMappingKey();
}
};
STATES[BLOCK_MAPPING_SIMPLE_VALUE] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectBlockMappingSimpleValue();
}
};
STATES[BLOCK_MAPPING_VALUE] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectBlockMappingValue();
}
};
STATES[BLOCK_MAPPING_KEY] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectBlockMappingKey(false);
}
};
STATES[FIRST_BLOCK_SEQUENCE_ITEM] = new EmitterState() {
public void expect(final EmitterEnvironment env) throws IOException {
env.expectBlockSequenceItem(true);
}
};
}
private final static Map DEFAULT_TAG_PREFIXES_1_0;
private final static Map DEFAULT_TAG_PREFIXES_1_1;
static {
final Map defInit0 = new HashMap();
defInit0.put("tag:yaml.org,2002:","!");
DEFAULT_TAG_PREFIXES_1_0 = java.util.Collections.unmodifiableMap(defInit0);
final Map defInit = new HashMap();
defInit.put("!","!");
defInit.put("tag:yaml.org,2002:","!!");
DEFAULT_TAG_PREFIXES_1_1 = java.util.Collections.unmodifiableMap(defInit);
}
private Writer stream;
private YAMLConfig options;
private EmitterEnvironment env;
public EmitterImpl(final Writer stream, final YAMLConfig opts) {
this.stream = stream;
this.options = opts;
this.env = new EmitterEnvironment();
this.env.emitter = this;
this.env.canonical = this.options.canonical();
final int propIndent = this.options.indent();
if(propIndent>=2 && propIndent<10) {
this.env.bestIndent = propIndent;
}
final int propWidth = this.options.bestWidth();
if(propWidth != 0 && propWidth > (this.env.bestIndent*2)) {
this.env.bestWidth = propWidth;
}
}
public YAMLConfig getOptions() {
return options;
}
public void emit(final Event event) throws IOException {
this.env.events.add(event);
while(!this.env.needMoreEvents()) {
this.env.event = (Event)this.env.events.remove(0);
STATES[this.env.state].expect(env);
this.env.event = null;
}
}
private static class EmitterEnvironment {
public List states = new ArrayList();
public int state = STREAM_START;
public List events = new ArrayList();
public Event event;
public int flowLevel = 0;
public List indents = new ArrayList();
public int indent = -1;
public boolean rootContext = false;
public boolean sequenceContext = false;
public boolean mappingContext = false;
public boolean simpleKeyContext = false;
public int line = 0;
public int column = 0;
public boolean whitespace = true;
public boolean indentation = true;
public boolean canonical = false;
public int bestIndent = 2;
public int bestWidth = 80;
public String bestLinebreak = "\n";
public Map tagPrefixes;
public String preparedAnchor;
public String preparedTag;
public ScalarAnalysis analysis;
public char style = 0;
public EmitterImpl emitter;
public String bestLineBreak = "\n";
public boolean isVersion10 = false;
public boolean needMoreEvents() {
if(events.isEmpty()) {
return true;
}
event = (Event)events.get(0);
if(event instanceof DocumentStartEvent) {
return needEvents(1);
} else if(event instanceof SequenceStartEvent) {
return needEvents(2);
} else if(event instanceof MappingStartEvent) {
return needEvents(3);
} else {
return false;
}
}
private boolean needEvents(final int count) {
int level = 0;
final Iterator iter = events.iterator();
iter.next();
for(;iter.hasNext();) {
final Object curr = iter.next();
if(curr instanceof DocumentStartEvent || curr instanceof CollectionStartEvent) {
level++;
} else if(curr instanceof DocumentEndEvent || curr instanceof CollectionEndEvent) {
level--;
} else if(curr instanceof StreamEndEvent) {
level = -1;
}
if(level<0) {
return false;
}
}
return events.size() < count+1;
}
private void increaseIndent(final boolean flow, final boolean indentless) {
indents.add(0,new Integer(indent));
if(indent == -1) {
if(flow) {
indent = bestIndent;
} else {
indent = 0;
}
} else if(!indentless) {
indent += bestIndent;
}
}
public void expectStreamStart() {
if(this.event instanceof StreamStartEvent) {
emitter.writeStreamStart();
this.state = FIRST_DOCUMENT_START;
} else {
throw new EmitterException("expected StreamStartEvent, but got " + this.event);
}
}
public void expectNothing() {
throw new EmitterException("expecting nothing, but got " + this.event);
}
public void expectDocumentStart(final boolean first) throws IOException {
if(event instanceof DocumentStartEvent) {
final DocumentStartEvent ev = (DocumentStartEvent)event;
if(first) {
if(null != ev.getVersion()) {
emitter.writeVersionDirective(prepareVersion(ev.getVersion()));
}
if((null != ev.getVersion() && ev.getVersion()[1] == 0) || emitter.getOptions().version().equals("1.0")) {
isVersion10 = true;
tagPrefixes = new HashMap(DEFAULT_TAG_PREFIXES_1_0);
} else {
tagPrefixes = new HashMap(DEFAULT_TAG_PREFIXES_1_1);
}
if(null != ev.getTags()) {
final Set handles = new TreeSet();
handles.addAll(ev.getTags().keySet());
for(final Iterator iter = handles.iterator();iter.hasNext();) {
final String handle = (String)iter.next();
final String prefix = (String)ev.getTags().get(handle);
tagPrefixes.put(prefix,handle);
final String handleText = prepareTagHandle(handle);
final String prefixText = prepareTagPrefix(prefix);
emitter.writeTagDirective(handleText,prefixText);
}
}
}
final boolean implicit = first && !ev.getExplicit() && !canonical && ev.getVersion() == null && ev.getTags() == null && !checkEmptyDocument();
if(!implicit) {
emitter.writeIndent();
emitter.writeIndicator("--- ",true,true,false);
if(canonical) {
emitter.writeIndent();
}
}
state = DOCUMENT_ROOT;
} else if(event instanceof StreamEndEvent) {
emitter.writeStreamEnd();
state = NOTHING;
} else {
throw new EmitterException("expected DocumentStartEvent, but got " + event);
}
}
public void expectDocumentRoot() throws IOException {
states.add(0,new Integer(DOCUMENT_END));
expectNode(true,false,false,false);
}
public void expectDocumentEnd() throws IOException {
if(event instanceof DocumentEndEvent) {
emitter.writeIndent();
if(((DocumentEndEvent)event).getExplicit()) {
emitter.writeIndicator("...",true,false,false);
emitter.writeIndent();
}
emitter.flushStream();
state = DOCUMENT_START;
} else {
throw new EmitterException("expected DocumentEndEvent, but got " + event);
}
}
public void expectFirstFlowSequenceItem() throws IOException {
if(event instanceof SequenceEndEvent) {
indent = ((Integer)indents.remove(0)).intValue();
flowLevel--;
emitter.writeIndicator("]",false,false,false);
state = ((Integer)states.remove(0)).intValue();
} else {
if(canonical || column > bestWidth) {
emitter.writeIndent();
}
states.add(0,new Integer(FLOW_SEQUENCE_ITEM));
expectNode(false,true,false,false);
}
}
public void expectFlowSequenceItem() throws IOException {
if(event instanceof SequenceEndEvent) {
indent = ((Integer)indents.remove(0)).intValue();
flowLevel--;
if(canonical) {
emitter.writeIndicator(",",false,false,false);
emitter.writeIndent();
}
emitter.writeIndicator("]",false,false,false);
state = ((Integer)states.remove(0)).intValue();
} else {
emitter.writeIndicator(",",false,false,false);
if(canonical || column > bestWidth) {
emitter.writeIndent();
}
states.add(0,new Integer(FLOW_SEQUENCE_ITEM));
expectNode(false,true,false,false);
}
}
public void expectFirstFlowMappingKey() throws IOException {
if(event instanceof MappingEndEvent) {
indent = ((Integer)indents.remove(0)).intValue();
flowLevel--;
emitter.writeIndicator("}",false,false,false);
state = ((Integer)states.remove(0)).intValue();
} else {
if(canonical || column > bestWidth) {
emitter.writeIndent();
}
if(!canonical && checkSimpleKey()) {
states.add(0,new Integer(FLOW_MAPPING_SIMPLE_VALUE));
expectNode(false,false,true,true);
} else {
emitter.writeIndicator("?",true,false,false);
states.add(0,new Integer(FLOW_MAPPING_VALUE));
expectNode(false,false,true,false);
}
}
}
public void expectFlowMappingSimpleValue() throws IOException {
emitter.writeIndicator(": ",false,true,false);
states.add(0,new Integer(FLOW_MAPPING_KEY));
expectNode(false,false,true,false);
}
public void expectFlowMappingValue() throws IOException {
if(canonical || column > bestWidth) {
emitter.writeIndent();
}
emitter.writeIndicator(": ",false,true,false);
states.add(0,new Integer(FLOW_MAPPING_KEY));
expectNode(false,false,true,false);
}
public void expectFlowMappingKey() throws IOException {
if(event instanceof MappingEndEvent) {
indent = ((Integer)indents.remove(0)).intValue();
flowLevel--;
if(canonical) {
emitter.writeIndicator(",",false,false,false);
emitter.writeIndent();
}
emitter.writeIndicator("}",false,false,false);
state = ((Integer)states.remove(0)).intValue();
} else {
emitter.writeIndicator(",",false,false,false);
if(canonical || column > bestWidth) {
emitter.writeIndent();
}
if(!canonical && checkSimpleKey()) {
states.add(0,new Integer(FLOW_MAPPING_SIMPLE_VALUE));
expectNode(false,false,true,true);
} else {
emitter.writeIndicator("?",true,false,false);
states.add(0,new Integer(FLOW_MAPPING_VALUE));
expectNode(false,false,true,false);
}
}
}
public void expectBlockSequenceItem(final boolean first) throws IOException {
if(!first && event instanceof SequenceEndEvent) {
indent = ((Integer)indents.remove(0)).intValue();
state = ((Integer)states.remove(0)).intValue();
} else {
emitter.writeIndent();
emitter.writeIndicator("-",true,false,true);
states.add(0,new Integer(BLOCK_SEQUENCE_ITEM));
expectNode(false,true,false,false);
}
}
public void expectFirstBlockMappingKey() throws IOException {
expectBlockMappingKey(true);
}
public void expectBlockMappingSimpleValue() throws IOException {
emitter.writeIndicator(": ",false,true,false);
states.add(0,new Integer(BLOCK_MAPPING_KEY));
expectNode(false,false,true,false);
}
public void expectBlockMappingValue() throws IOException {
emitter.writeIndent();
emitter.writeIndicator(": ",true,true,true);
states.add(0,new Integer(BLOCK_MAPPING_KEY));
expectNode(false,false,true,false);
}
public void expectBlockMappingKey(final boolean first) throws IOException {
if(!first && event instanceof MappingEndEvent) {
indent = ((Integer)indents.remove(0)).intValue();
state = ((Integer)states.remove(0)).intValue();
} else {
emitter.writeIndent();
if(checkSimpleKey()) {
states.add(0,new Integer(BLOCK_MAPPING_SIMPLE_VALUE));
expectNode(false,false,true,true);
} else {
emitter.writeIndicator("?",true,false,true);
states.add(0,new Integer(BLOCK_MAPPING_VALUE));
expectNode(false,false,true,false);
}
}
}
private void expectNode(final boolean root, final boolean sequence, final boolean mapping, final boolean simpleKey) throws IOException {
rootContext = root;
sequenceContext = sequence;
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).getFlowStyle() || checkEmptySequence()) {
expectFlowSequence();
} else {
expectBlockSequence();
}
} else if(event instanceof MappingStartEvent) {
if(flowLevel != 0 || canonical || ((MappingStartEvent)event).getFlowStyle() || checkEmptyMapping()) {
expectFlowMapping();
} else {
expectBlockMapping();
}
}
} else {
throw new EmitterException("expected NodeEvent, but got " + event);
}
}
private void expectAlias() throws IOException {
if(((NodeEvent)event).getAnchor() == null) {
throw new EmitterException("anchor is not specified for alias");
}
processAnchor("*");
state = ((Integer)states.remove(0)).intValue();
}
private void expectScalar() throws IOException {
increaseIndent(true,false);
processScalar();
indent = ((Integer)indents.remove(0)).intValue();
state = ((Integer)states.remove(0)).intValue();
}
private void expectFlowSequence() throws IOException {
emitter.writeIndicator("[",true,true,false);
flowLevel++;
increaseIndent(true,false);
state = FIRST_FLOW_SEQUENCE_ITEM;
}
private void expectBlockSequence() throws IOException {
increaseIndent(false, mappingContext && !indentation);
state = FIRST_BLOCK_SEQUENCE_ITEM;
}
private void expectFlowMapping() throws IOException {
emitter.writeIndicator("{",true,true,false);
flowLevel++;
increaseIndent(true,false);
state = FIRST_FLOW_MAPPING_KEY;
}
private void expectBlockMapping() throws IOException {
increaseIndent(false,false);
state = FIRST_BLOCK_MAPPING_KEY;
}
private boolean checkEmptySequence() {
return event instanceof SequenceStartEvent && !events.isEmpty() && events.get(0) instanceof SequenceEndEvent;
}
private boolean checkEmptyMapping() {
return event instanceof MappingStartEvent && !events.isEmpty() && events.get(0) instanceof MappingEndEvent;
}
private boolean checkEmptyDocument() {
if(!(event instanceof DocumentStartEvent) || events.isEmpty()) {
return false;
}
final Event ev = (Event)events.get(0);
return ev instanceof ScalarEvent && ((ScalarEvent)ev).getAnchor() == null && ((ScalarEvent)ev).getTag() == null && ((ScalarEvent)ev).getImplicit() != null && ((ScalarEvent)ev).getValue().equals("");
}
private boolean checkSimpleKey() {
int length = 0;
if(event instanceof NodeEvent && null != ((NodeEvent)event).getAnchor()) {
if(null == preparedAnchor) {
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(null == preparedTag) {
preparedTag = emitter.prepareTag(tag);
}
length += preparedTag.length();
}
if(event instanceof ScalarEvent) {
if(null == analysis) {
analysis = analyzeScalar(((ScalarEvent)event).getValue());
length += analysis.scalar.length();
}
}
return (length < 128 && (event instanceof AliasEvent || (event instanceof ScalarEvent && !analysis.empty && !analysis.multiline) || checkEmptySequence() || checkEmptyMapping()));
}
private void processAnchor(final String indicator) throws IOException {
final NodeEvent ev = (NodeEvent)event;
if(null == ev.getAnchor()) {
preparedAnchor = null;
return;
}
if(null == preparedAnchor) {
preparedAnchor = prepareAnchor(ev.getAnchor());
}
if(preparedAnchor != null && !"".equals(preparedAnchor)) {
emitter.writeIndicator(indicator+preparedAnchor,true,false,false);
}
preparedAnchor = null;
}
private void processTag() throws IOException {
String tag = null;
if(event instanceof ScalarEvent) {
final ScalarEvent ev = (ScalarEvent)event;
tag = ev.getTag();
if(style == 0) {
style = chooseScalarStyle();
}
if(((!canonical || tag == null) && ((0 == style && ev.getImplicit()[0]) || (0 != style && ev.getImplicit()[1])))) {
preparedTag = null;
return;
}
if(ev.getImplicit()[0] && null == tag) {
tag = "!";
preparedTag = null;
}
} else {
final 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(null == preparedTag) {
preparedTag = emitter.prepareTag(tag);
}
if(preparedTag != null && !"".equals(preparedTag)) {
emitter.writeIndicator(preparedTag,true,false,false);
}
preparedTag = null;
}
private char chooseScalarStyle() {
final ScalarEvent ev = (ScalarEvent)event;
if(null == analysis) {
analysis = analyzeScalar(ev.getValue());
}
if(ev.getStyle() == '"' || this.canonical) {
return '"';
}
// if(ev.getStyle() == 0 && ev.getImplicit()[0]) {
if(ev.getStyle() == 0) {
if(!(simpleKeyContext && (analysis.empty || analysis.multiline)) && ((flowLevel != 0 && analysis.allowFlowPlain) || (flowLevel == 0 && analysis.allowBlockPlain))) {
return 0;
}
}
if(ev.getStyle() == 0 && ev.getImplicit()[0] && (!(simpleKeyContext && (analysis.empty || analysis.multiline)) && (flowLevel!=0 && analysis.allowFlowPlain || (flowLevel == 0 && analysis.allowBlockPlain)))) {
return 0;
}
if((ev.getStyle() == '|' || ev.getStyle() == '>') && flowLevel == 0 && analysis.allowBlock) {
return '\'';
}
if((ev.getStyle() == 0 || ev.getStyle() == '\'') && (analysis.allowSingleQuoted && !(simpleKeyContext && analysis.multiline))) {
return '\'';
}
return '"';
}
private void processScalar() throws IOException {
final ScalarEvent ev = (ScalarEvent)event;
if(null == analysis) {
analysis = analyzeScalar(ev.getValue());
}
if(0 == style) {
style = chooseScalarStyle();
}
final boolean split = !simpleKeyContext;
if(style == '"') {
emitter.writeDoubleQuoted(analysis.scalar,split);
} else if(style == '\'') {
emitter.writeSingleQuoted(analysis.scalar,split);
} else if(style == '>') {
emitter.writeFolded(analysis.scalar);
} else if(style == '|') {
emitter.writeLiteral(analysis.scalar);
} else {
emitter.writePlain(analysis.scalar,split);
}
analysis = null;
style = 0;
}
}
void writeStreamStart() {
}
void writeStreamEnd() throws IOException {
flushStream();
}
void writeIndicator(final String indicator, final boolean needWhitespace, final boolean whitespace, final boolean indentation) throws IOException {
String data = null;
if(env.whitespace || !needWhitespace) {
data = indicator;
} else {
data = " " + indicator;
}
env.whitespace = whitespace;
env.indentation = env.indentation && indentation;
env.column += data.length();
stream.write(data);
}
void writeIndent() throws IOException {
int indent = 0;
if(env.indent != -1) {
indent = env.indent;
}
if(!env.indentation || env.column > indent || (env.column == indent && !env.whitespace)) {
writeLineBreak(null);
}
if(env.column < indent) {
env.whitespace = true;
final StringBuffer data = new StringBuffer();
for(int i=0,j=(indent-env.column);i<j;i++) {
data.append(" ");
}
env.column = indent;
stream.write(data.toString());
}
}
void writeVersionDirective(final String version_text) throws IOException {
stream.write("%YAML " + version_text);
writeLineBreak(null);
}
void writeTagDirective(final String handle, final String prefix) throws IOException {
stream.write("%TAG " + handle + " " + prefix);
writeLineBreak(null);
}
void writeDoubleQuoted(final String text, final boolean split) throws IOException {
writeIndicator("\"",true,false,false);
int start = 0;
int ending = 0;
String data = null;
while(ending <= text.length()) {
char ch = 0;
if(ending < text.length()) {
ch = text.charAt(ending);
}
if(ch==0 || "\"\\\u0085".indexOf(ch) != -1 || !('\u0020' <= ch && ch <= '\u007E')) {
if(start < ending) {
data = text.substring(start,ending);
env.column+=data.length();
stream.write(data);
start = ending;
}
if(ch != 0) {
if(YAML.ESCAPE_REPLACEMENTS.containsKey(new Character(ch))) {
data = "\\" + YAML.ESCAPE_REPLACEMENTS.get(new Character(ch));
} else if(ch <= '\u00FF') {
String str = Integer.toString(ch,16);
if(str.length() == 1) {
str = "0" + str;
}
data = "\\x" + str;
}
env.column += data.length();
stream.write(data);
start = ending+1;
}
}
if((0 < ending && ending < (text.length()-1)) && (ch == ' ' || start >= ending) && (env.column+(ending-start)) > env.bestWidth && split) {
data = text.substring(start,ending) + "\\";
if(start < ending) {
start = ending;
}
env.column += data.length();
stream.write(data);
writeIndent();
env.whitespace = false;
env.indentation = false;
if(text.charAt(start) == ' ') {
data = "\\";
env.column += data.length();
stream.write(data);
}
}
ending += 1;
}
writeIndicator("\"",false,false,false);
}
void writeSingleQuoted(final String text, final boolean split) throws IOException {
writeIndicator("'",true,false,false);
boolean spaces = false;
boolean breaks = false;
int start=0,ending=0;
char ceh = 0;
String data = null;
while(ending <= text.length()) {
ceh = 0;
if(ending < text.length()) {
ceh = text.charAt(ending);
}
if(spaces) {
if(ceh == 0 || ceh != 32) {
if(start+1 == ending && env.column > env.bestWidth && split && start != 0 && ending != text.length()) {
writeIndent();
} else {
data = text.substring(start,ending);
env.column += data.length();
stream.write(data);
}
start = ending;
}
} else if(breaks) {
if(ceh == 0 || !('\n' == ceh || '\u0085' == ceh)) {
data = text.substring(start,ending);
for(int i=0,j=data.length();i<j;i++) {
char cha = data.charAt(i);
if('\n' == cha) {
writeLineBreak(null);
} else {
writeLineBreak(""+cha);
}
}
writeIndent();
start = ending;
}
} else {
if(ceh == 0 || !('\n' == ceh || '\u0085' == ceh)) {
if(start < ending) {
data = text.substring(start,ending);
env.column += data.length();
stream.write(data);
start = ending;
}
if(ceh == '\'') {
data = "''";
env.column += 2;
stream.write(data);
start = ending + 1;
}
}
}
if(ceh != 0) {
spaces = ceh == ' ';
breaks = ceh == '\n' || ceh == '\u0085';
}
ending++;
}
writeIndicator("'",false,false,false);
}
void writeFolded(final String text) throws IOException {
String chomp = determineChomp(text);
writeIndicator(">" + chomp, true, false, false);
writeIndent();
boolean leadingSpace = false;
boolean spaces = false;
boolean breaks = false;
int start=0,ending=0;
String data = null;
while(ending <= text.length()) {
char ceh = 0;
if(ending < text.length()) {
ceh = text.charAt(ending);
}
if(breaks) {
if(ceh == 0 || !('\n' == ceh || '\u0085' == ceh)) {
if(!leadingSpace && ceh != 0 && ceh != ' ' && text.charAt(start) == '\n') {
writeLineBreak(null);
}
leadingSpace = ceh == ' ';
data = text.substring(start,ending);
for(int i=0,j=data.length();i<j;i++) {
char cha = data.charAt(i);
if('\n' == cha) {
writeLineBreak(null);
} else {
writeLineBreak(""+cha);
}
}
if(ceh != 0) {
writeIndent();
}
start = ending;
}
} else if(spaces) {
if(ceh != ' ') {
if(start+1 == ending && env.column > env.bestWidth) {
writeIndent();
} else {
data = text.substring(start,ending);
env.column += data.length();
stream.write(data);
}
start = ending;
}
} else {
if(ceh == 0 || ' ' == ceh || '\n' == ceh || '\u0085' == ceh) {
data = text.substring(start,ending);
stream.write(data);
if(ceh == 0) {
writeLineBreak(null);
}
start = ending;
}
}
if(ceh != 0) {
breaks = '\n' == ceh || '\u0085' == ceh;
spaces = ceh == ' ';
}
ending++;
}
}
void writeLiteral(final String text) throws IOException {
String chomp = determineChomp(text);
writeIndicator("|" + chomp, true, false, false);
writeIndent();
boolean breaks = false;
int start=0,ending=0;
String data = null;
while(ending <= text.length()) {
char ceh = 0;
if(ending < text.length()) {
ceh = text.charAt(ending);
}
if(breaks) {
if(ceh == 0 || !('\n' == ceh || '\u0085' == ceh)) {
data = text.substring(start,ending);
for(int i=0,j=data.length();i<j;i++) {
char cha = data.charAt(i);
if('\n' == cha) {
writeLineBreak(null);
} else {
writeLineBreak(""+cha);
}
}
if(ceh != 0) {
writeIndent();
}
start = ending;
}
} else {
if(ceh == 0 || '\n' == ceh || '\u0085' == ceh) {
data = text.substring(start,ending);
stream.write(data);
if(ceh == 0) {
writeLineBreak(null);
}
start = ending;
}
}
if(ceh != 0) {
breaks = '\n' == ceh || '\u0085' == ceh;
}
ending++;
}
}
void writePlain(final String text, final boolean split) throws IOException {
if(text == null || "".equals(text)) {
return;
}
String data = null;
if(!env.whitespace) {
data = " ";
env.column += data.length();
stream.write(data);
}
env.whitespace = false;
env.indentation = false;
boolean spaces=false, breaks = false;
int start=0,ending=0;
while(ending <= text.length()) {
char ceh = 0;
if(ending < text.length()) {
ceh = text.charAt(ending);
}
if(spaces) {
if(ceh != ' ') {
if(start+1 == ending && env.column > env.bestWidth && split) {
writeIndent();
env.whitespace = false;
env.indentation = false;
} else {
data = text.substring(start,ending);
env.column += data.length();
stream.write(data);
}
start = ending;
}
} else if(breaks) {
if(ceh != '\n' && ceh != '\u0085') {
if(text.charAt(start) == '\n') {
writeLineBreak(null);
}
data = text.substring(start,ending);
for(int i=0,j=data.length();i<j;i++) {
char cha = data.charAt(i);
if('\n' == cha) {
writeLineBreak(null);
} else {
writeLineBreak(""+cha);
}
}
writeIndent();
env.whitespace = false;
env.indentation = false;
start = ending;
}
} else {
if(ceh == 0 || ' ' == ceh || '\n' == ceh || '\u0085' == ceh) {
data = text.substring(start,ending);
env.column += data.length();
stream.write(data);
start = ending;
}
}
if(ceh != 0) {
spaces = ceh == ' ';
breaks = ceh == '\n' || ceh == '\u0085';
}
ending++;
}
}
void writeLineBreak(final String data) throws IOException {
String xdata = data;
if(xdata == null) {
xdata = env.bestLineBreak;
}
env.whitespace = true;
env.indentation = true;
env.line++;
env.column = 0;
stream.write(xdata);
}
void flushStream() throws IOException {
stream.flush();
}
static String prepareVersion(final int[] version) {
if(version[0] != 1) {
throw new EmitterException("unsupported YAML version: " + version[0] + "." + version[1]);
}
return ""+version[0] + "." + version[1];
}
private final static Pattern HANDLE_FORMAT = Pattern.compile("^![-\\w]*!$");
static String prepareTagHandle(final String handle) {
if(handle == null || "".equals(handle)) {
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 syntax for tag handle: " + handle);
}
return handle;
}
static String prepareTagPrefix(final String prefix) {
if(prefix == null || "".equals(prefix)) {
throw new EmitterException("tag prefix must not be empty");
}
final StringBuffer chunks = new StringBuffer();
int start=0,ending=0;
if(prefix.charAt(0) == '!') {
ending = 1;
}
while(ending < prefix.length()) {
ending++;
}
if(start < ending) {
chunks.append(prefix.substring(start,ending));
}
return chunks.toString();
}
private final static Pattern ANCHOR_FORMAT = Pattern.compile("^[-\\w]*$");
static String prepareAnchor(final String anchor) {
if(anchor == null || "".equals(anchor)) {
throw new EmitterException("anchor must not be empty");
}
if(!ANCHOR_FORMAT.matcher(anchor).matches()) {
throw new EmitterException("invalid syntax for anchor: " + anchor);
}
return anchor;
}
String prepareTag(final String tag) {
if(tag == null || "".equals(tag)) {
throw new EmitterException("tag must not be empty");
}
if(tag.equals("!")) {
return tag;
}
String handle = null;
String suffix = tag;
for(final Iterator iter = env.tagPrefixes.keySet().iterator();iter.hasNext();) {
String prefix = (String)iter.next();
if(Pattern.matches("^" + prefix + ".+$", tag) && (prefix.equals("!") || prefix.length() < tag.length())) {
handle = (String)env.tagPrefixes.get(prefix);
suffix = tag.substring(prefix.length());
}
}
final StringBuffer chunks = new StringBuffer();
int start=0,ending=0;
while(ending < suffix.length()) {
ending++;
}
if(start < ending) {
chunks.append(suffix.substring(start,ending));
}
String suffixText = chunks.toString();
if(tag.charAt(0) == '!' && env.isVersion10) {
return tag;
}
if(handle != null) {
return handle + suffixText;
} else {
return "!<" + suffixText + ">";
}
}
private final static Pattern DOC_INDIC = Pattern.compile("^(---|\\.\\.\\.)");
private final static String NULL_BL_T_LINEBR = "\0 \t\r\n\u0085";
private final static String SPECIAL_INDIC = "#,[]{}#&*!|>'\"%@`";
private final static String FLOW_INDIC = ",?[]{}";
static ScalarAnalysis analyzeScalar(final String scalar) {
if(scalar == null || "".equals(scalar)) {
return new ScalarAnalysis(scalar,true,false,false,true,true,true,false);
}
boolean blockIndicators = false;
boolean flowIndicators = false;
boolean lineBreaks = false;
boolean specialCharacters = false;
// Whitespaces.
boolean inlineSpaces = false; // non-space space+ non-space
boolean inlineBreaks = false; // non-space break+ non-space
boolean leadingSpaces = false; // ^ space+ (non-space | $)
boolean leadingBreaks = false; // ^ break+ (non-space | $)
boolean trailingSpaces = false; // (^ | non-space) space+ $
boolean trailingBreaks = false; // (^ | non-space) break+ $
boolean inlineBreaksSpaces = false; // non-space break+ space+ non-space
boolean mixedBreaksSpaces = false; // anything else
if(DOC_INDIC.matcher(scalar).matches()) {
blockIndicators = true;
flowIndicators = true;
}
boolean preceededBySpace = true;
boolean followedBySpace = scalar.length() == 1 || NULL_BL_T_LINEBR.indexOf(scalar.charAt(1)) != -1;
boolean spaces = false;
boolean breaks = false;
boolean mixed = false;
boolean leading = false;
int index = 0;
while(index < scalar.length()) {
char ceh = scalar.charAt(index);
if(index == 0) {
if(SPECIAL_INDIC.indexOf(ceh) != -1) {
flowIndicators = true;
blockIndicators = true;
}
if(ceh == '?' || ceh == ':') {
flowIndicators = true;
if(followedBySpace) {
blockIndicators = true;
}
}
if(ceh == '-' && followedBySpace) {
flowIndicators = true;
blockIndicators = true;
}
} else {
if(FLOW_INDIC.indexOf(ceh) != -1) {
flowIndicators = true;
}
if(ceh == ':') {
flowIndicators = true;
if(followedBySpace) {
blockIndicators = true;
}
}
if(ceh == '#' && preceededBySpace) {
flowIndicators = true;
blockIndicators = true;
}
}
if(ceh == '\n' || '\u0085' == ceh) {
lineBreaks = true;
}
if(!(ceh == '\n' || ('\u0020' <= ceh && ceh <= '\u007E'))) {
specialCharacters = true;
}
if(' ' == ceh || '\n' == ceh || '\u0085' == ceh) {
if(spaces && breaks) {
if(ceh != ' ') {
mixed = true;
}
} else if(spaces) {
if(ceh != ' ') {
breaks = true;
mixed = true;
}
} else if(breaks) {
if(ceh == ' ') {
spaces = true;
}
} else {
leading = (index == 0);
if(ceh == ' ') {
spaces = true;
} else {
breaks = true;
}
}
} else if(spaces || breaks) {
if(leading) {
if(spaces && breaks) {
mixedBreaksSpaces = true;
} else if(spaces) {
leadingSpaces = true;
} else if(breaks) {
leadingBreaks = true;
}
} else {
if(mixed) {
mixedBreaksSpaces = true;
} else if(spaces && breaks) {
inlineBreaksSpaces = true;
} else if(spaces) {
inlineSpaces = true;
} else if(breaks) {
inlineBreaks = true;
}
}
spaces = breaks = mixed = leading = false;
}
if((spaces || breaks) && (index == scalar.length()-1)) {
if(spaces && breaks) {
mixedBreaksSpaces = true;
} else if(spaces) {
trailingSpaces = true;
if(leading) {
leadingSpaces = true;
}
} else if(breaks) {
trailingBreaks = true;
if(leading) {
leadingBreaks = true;
}
}
spaces = breaks = mixed = leading = false;
}
index++;
preceededBySpace = NULL_BL_T_LINEBR.indexOf(ceh) != -1;
followedBySpace = index+1 >= scalar.length() || NULL_BL_T_LINEBR.indexOf(scalar.charAt(index+1)) != -1;
}
boolean allowFlowPlain = true;
boolean allowBlockPlain = true;
boolean allowSingleQuoted = true;
boolean allowDoubleQuoted = true;
boolean allowBlock = true;
if(leadingSpaces || leadingBreaks || trailingSpaces) {
allowFlowPlain = allowBlockPlain = allowBlock = false;
}
if(trailingBreaks) {
allowFlowPlain = allowBlockPlain = false;
}
if(inlineBreaksSpaces) {
allowFlowPlain = allowBlockPlain = allowSingleQuoted = false;
}
if(mixedBreaksSpaces || specialCharacters) {
allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false;
}
if(inlineBreaks) {
allowFlowPlain = allowBlockPlain = allowSingleQuoted = false;
}
if(trailingBreaks) {
allowSingleQuoted = false;
}
if(lineBreaks) {
allowFlowPlain = allowBlockPlain = false;
}
if(flowIndicators) {
allowFlowPlain = false;
}
if(blockIndicators) {
allowBlockPlain = false;
}
return new ScalarAnalysis(scalar,false,lineBreaks,allowFlowPlain,allowBlockPlain,allowSingleQuoted,allowDoubleQuoted,allowBlock);
}
static String determineChomp(final String text) {
String tail = text.substring(text.length()-2,text.length()-1);
while(tail.length() < 2) {
tail = " " + tail;
}
char ceh = tail.charAt(tail.length()-1);
char ceh2 = tail.charAt(tail.length()-2);
return (ceh == '\n' || ceh == '\u0085') ? ((ceh2 == '\n' || ceh2 == '\u0085') ? "+" : "") : "-";
}
public static void main(final String[] args) throws IOException {
final String filename = args[0]; // filename to test against
System.out.println("File contents:");
final BufferedReader read = new BufferedReader(new FileReader(filename));
String last = null;
while((last = read.readLine()) != null) {
System.out.println(last);
}
read.close();
System.out.println("--------------------------------");
final Emitter emitter = new EmitterImpl(new java.io.OutputStreamWriter(System.out),YAML.config());
final Parser pars = new ParserImpl(new ScannerImpl(new FileReader(filename)));
for(final Iterator iter = pars.eachEvent();iter.hasNext();) {
emitter.emit((Event)iter.next());
}
}
}// EmitterImpl