Skip to content

Commit

Permalink
Additional options to allow control over style of generated YAML
Browse files Browse the repository at this point in the history
Following same options and process used by
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml

Signed-off-by: Michael Edgar <michael@xlate.io>
  • Loading branch information
MikeEdgar committed Feb 28, 2024
1 parent 6ba6bcd commit 26773b2
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 94 deletions.
8 changes: 4 additions & 4 deletions src/main/java/io/xlate/yamljson/SnakeYamlEngineGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ class SnakeYamlEngineGenerator extends YamlGenerator<Event, ScalarStyle> impleme
final DumpSettings settings;
final Emitter emitter;

SnakeYamlEngineGenerator(DumpSettings settings, YamlWriterStream writer) {
super(STYLES, writer);
SnakeYamlEngineGenerator(Map<String, Object> properties, DumpSettings settings, YamlWriterStream writer) {
super(properties, STYLES, writer);
this.settings = settings;
this.emitter = new Emitter(settings, writer);
}

SnakeYamlEngineGenerator(DumpSettings settings, Writer writer) {
this(settings, new YamlWriterStream(writer));
SnakeYamlEngineGenerator(Map<String, Object> properties, DumpSettings settings, Writer writer) {
this(properties, settings, new YamlWriterStream(writer));
}

@Override
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/xlate/yamljson/SnakeYamlGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ class SnakeYamlGenerator extends YamlGenerator<Event, ScalarStyle> implements Js
final DumperOptions settings;
final Emitter emitter;

SnakeYamlGenerator(DumperOptions settings, Writer writer) {
super(STYLES, writer);
SnakeYamlGenerator(Map<String, Object> properties, DumperOptions settings, Writer writer) {
super(properties, STYLES, writer);
this.settings = settings;
this.emitter = new Emitter(writer, settings);
}
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/io/xlate/yamljson/StringQuotingChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,32 @@
*/
class StringQuotingChecker {

StringQuotingChecker() {
private final boolean quoteNumericStrings;

StringQuotingChecker(boolean quoteNumericStrings) {
this.quoteNumericStrings = quoteNumericStrings;
}

/**
* Check whether given property name should be quoted: usually to prevent it
* from being read as non-String key (boolean or number)
*/
boolean needToQuoteName(String name) {
return isReservedKeyword(name) || YamlNumbers.isFloat(name);
return needToQuote(name, true);
}

/**
* Check whether given String value should be quoted: usually to prevent it
* from being value of different type (boolean or number).
*/
boolean needToQuoteValue(String value) {
// Only consider reserved keywords but not numbers?
return isReservedKeyword(value) || valueHasQuotableChar(value);
return needToQuote(value, quoteNumericStrings);
}

boolean needToQuote(String value, boolean quoteNumeric) {
return isReservedKeyword(value) ||
hasQuoteableCharacter(value) ||
(quoteNumeric && YamlNumbers.isNumeric(value));
}

/**
Expand Down Expand Up @@ -67,7 +75,7 @@ boolean isReservedKeyword(String value) {
* quoted in case they contain one of the following characters or character
* combinations.
*/
boolean valueHasQuotableChar(String inputStr) {
boolean hasQuoteableCharacter(String inputStr) {
final int end = inputStr.length();
for (int i = 0; i < end; ++i) {
switch (inputStr.charAt(i)) {
Expand Down
52 changes: 49 additions & 3 deletions src/main/java/io/xlate/yamljson/Yaml.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ private Versions() {
*/
public static final class Settings {

private static final String PRE = "io.xlate.yamljson.";

private Settings() {
}

Expand All @@ -111,7 +113,7 @@ private Settings() {
* @see Versions#V1_1
* @see Versions#V1_2
*/
public static final String YAML_VERSION = "io.xlate.yamljson.YAML_VERSION";
public static final String YAML_VERSION = PRE + "YAML_VERSION";

/**
* The maximum number of scalars to which an alias (or chain of aliases via arrays/objects)
Expand Down Expand Up @@ -177,7 +179,7 @@ private Settings() {
*
* @since 0.2
*/
public static final String LOAD_CONFIG = "io.xlate.yamljson.LOAD_CONFIG";
public static final String LOAD_CONFIG = PRE + "LOAD_CONFIG";

/**
* Used to pass a pre-configured
Expand All @@ -187,8 +189,52 @@ private Settings() {
*
* @since 0.2
*/
public static final String DUMP_CONFIG = "io.xlate.yamljson.DUMP_CONFIG";
public static final String DUMP_CONFIG = PRE + "DUMP_CONFIG";

/**
* Whether strings will be rendered without quotes (true) or with quotes
* (false, default).
* <p>
* Minimized quote usage makes for more human readable output; however,
* content is limited to printable characters according to the rules of
* <a href=
* "http://www.yaml.org/spec/1.2/spec.html#style/block/literal">literal
* block style</a>.
*
* @since 0.2
*/
public static final String DUMP_MINIMIZE_QUOTES = PRE + "DUMP_MINIMIZE_QUOTES";

/**
* Whether numeric values stored as strings will be rendered with quotes
* (true, default) or without quotes (false) when
* {@link #DUMP_MINIMIZE_QUOTES} is enabled.
*
* @since 0.2
*/
public static final String DUMP_QUOTE_NUMERIC_STRINGS = PRE + "DUMP_QUOTE_NUMERIC_STRINGS";

/**
* Whether strings containing newlines should use <a href=
* "http://www.yaml.org/spec/1.2/spec.html#style/block/literal">literal
* block style</a>. This automatically enabled when
* {@link #DUMP_MINIMIZE_QUOTES} is set.
*
* @since 0.2
*/
public static final String DUMP_LITERAL_BLOCK_STYLE = PRE + "DUMP_LITERAL_BLOCK_STYLE";

/**
* Feature that determines whether {@link java.math.BigDecimal} entries are
* serialized using {@link java.math.BigDecimal#toPlainString()} to prevent
* values to be written using scientific notation.
*<p>
* Feature is disabled by default, so default output mode is used; this generally
* depends on how {@link java.math.BigDecimal} has been created.
*
* @since 0.2
*/
public static final String DUMP_WRITE_PLAIN_BIGDECIMAL = PRE + "DUMP_WRITE_PLAIN_BIGDECIMAL";
}

private static final YamlProvider PROVIDER = new YamlProvider();
Expand Down
60 changes: 39 additions & 21 deletions src/main/java/io/xlate/yamljson/YamlGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,34 @@ interface IOOperation {
}

static final String VALUE = "value";
static final String FALSE = "false";
static final String TRUE = "true";

static final StringQuotingChecker quoteChecker = new StringQuotingChecker();

protected final Map<String, Object> properties;
protected final Map<StyleType, S> styleTypes;
protected final Writer writer;
final Deque<ContextType> context = new ArrayDeque<>();

YamlGenerator(Map<StyleType, S> styleTypes, Writer writer) {
private final Deque<ContextType> context = new ArrayDeque<>();
private final boolean minimizeQuotes;
private final boolean quoteNumericStrings;
private final boolean literalBlockStyle;
private final boolean writePlainBigDecimal;
private final StringQuotingChecker quoteChecker;

YamlGenerator(Map<String, Object> properties, Map<StyleType, S> styleTypes, Writer writer) {
this.properties = properties;
this.styleTypes = styleTypes;
this.writer = writer;
this.minimizeQuotes = parse(properties, Yaml.Settings.DUMP_MINIMIZE_QUOTES, FALSE);
this.quoteNumericStrings = parse(properties, Yaml.Settings.DUMP_QUOTE_NUMERIC_STRINGS, TRUE);
this.literalBlockStyle = parse(properties, Yaml.Settings.DUMP_LITERAL_BLOCK_STYLE, FALSE);
this.writePlainBigDecimal = parse(properties, Yaml.Settings.DUMP_WRITE_PLAIN_BIGDECIMAL, FALSE);
this.quoteChecker = new StringQuotingChecker(quoteNumericStrings);
}

static boolean parse(Map<String, Object> properties, String key, String defaultValue) {
Object value = properties.getOrDefault(key, defaultValue);
return Boolean.parseBoolean(String.valueOf(value));
}

protected abstract E getEvent(EventType type);
Expand Down Expand Up @@ -122,23 +140,22 @@ void emitScalar(Object value, boolean forcePlain, Predicate<String> quoteCheck)
style = styleTypes.get(StyleType.PLAIN);
} else {
scalarValue = String.valueOf(value);
boolean containsNewLine = scalarValue.indexOf('\n') > -1;
boolean containsDoubleQuote = scalarValue.indexOf('"') > -1;
boolean containsSingleQuote = scalarValue.indexOf('\'') > -1;

if (containsNewLine) {
// XXX: Allow for folded scalar style via configuration
style = styleTypes.get(StyleType.LITERAL);
} else if (containsDoubleQuote && containsSingleQuote) {
style = styleTypes.get(StyleType.LITERAL);
} else if (containsDoubleQuote) {
style = styleTypes.get(StyleType.SINGLE_QUOTED);
} else if (containsSingleQuote) {
style = styleTypes.get(StyleType.DOUBLE_QUOTED);
} else if (quoteCheck.test(scalarValue)) {
style = styleTypes.get(StyleType.SINGLE_QUOTED);

if (minimizeQuotes) {
if (scalarValue.indexOf('\n') >= 0) {
style = styleTypes.get(StyleType.LITERAL);
} else if (quoteCheck.test(scalarValue)) {
// Preserve quotes for keywords, indicators, and numeric strings (if configured)
style = styleTypes.get(StyleType.DOUBLE_QUOTED);
} else {
style = styleTypes.get(StyleType.PLAIN);
}
} else {
style = styleTypes.get(StyleType.PLAIN);
if (literalBlockStyle && scalarValue.indexOf('\n') >= 0) {
style = styleTypes.get(StyleType.LITERAL);
} else {
style = styleTypes.get(StyleType.DOUBLE_QUOTED);
}
}
}

Expand Down Expand Up @@ -329,7 +346,8 @@ public JsonGenerator write(String value) {
@Override
public JsonGenerator write(BigDecimal value) {
Objects.requireNonNull(value, VALUE);
emitScalar(value);
Object stringValue = writePlainBigDecimal ? value.toPlainString() : value.toString();
emitScalar(stringValue);
return this;
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/xlate/yamljson/YamlGeneratorFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ public JsonGenerator createGenerator(Writer writer) {

if (useSnakeYamlEngine) {
var settings = (org.snakeyaml.engine.v2.api.DumpSettings) this.snakeYamlSettings;
return new SnakeYamlEngineGenerator(settings, writer);
return new SnakeYamlEngineGenerator(properties, settings, writer);
}

var settings = (org.yaml.snakeyaml.DumperOptions) this.snakeYamlSettings;
return new SnakeYamlGenerator(settings, writer);
return new SnakeYamlGenerator(properties, settings, writer);
}

@Override
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/io/xlate/yamljson/YamlNumbers.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ final class YamlNumbers {
private YamlNumbers() {
}

static boolean isNumeric(String dataText) {
return isFloat(dataText) || isSpecial(dataText);
}

static boolean isSpecial(String dataText) {
return YamlParser.VALUES_INFINITY.contains(dataText) || YamlParser.VALUES_NAN.contains(dataText);
}

static boolean isInteger(String dataText) {
final int start;

Expand Down
Loading

0 comments on commit 26773b2

Please sign in to comment.