xjs.serialization.parser.CommentedTokenParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xjs-core Show documentation
Show all versions of xjs-core Show documentation
Core data layer and serlializers for XJS, an elegant JSON superset.
package xjs.serialization.parser;
import xjs.comments.Comment;
import xjs.comments.CommentData;
import xjs.comments.CommentType;
import xjs.core.JsonObject;
import xjs.serialization.token.CommentToken;
import xjs.serialization.token.Token;
import xjs.serialization.token.TokenStream;
import xjs.serialization.token.TokenType;
/**
* Specialized implementation of {@link TokenParser} designed to tolerate
* commented JSON formats. This class provides utilities for storing and
* applying comments to the output, as well as overrides to neatly mesh
* this logic into the existing interface where possible.
*
* @see TokenParser
*/
public abstract class CommentedTokenParser extends TokenParser {
protected CommentData commentBuffer;
protected CommentedTokenParser(final TokenStream root) {
super(root);
this.commentBuffer = new CommentData();
}
@Override
protected boolean consumeWhitespace(
final Token t, final boolean nl) {
if (!t.isMetadata()) {
return false;
}
if (t.type() == TokenType.BREAK) {
if (!nl) {
return false;
}
this.flagLineAsSkipped();
} else {
this.appendComment((CommentToken) t);
}
return true;
}
@Override
protected void flagLineAsSkipped() {
if (!this.commentBuffer.isEmpty()) {
this.commentBuffer.append(1);
} else {
this.linesSkipped++;
}
}
@Override
protected void setAbove() {
this.setComment(CommentType.HEADER);
super.setAbove();
}
@Override
protected void setBetween() {
this.setComment(CommentType.VALUE);
super.setBetween();
}
@Override
protected void setTrailing() {
this.setComment(CommentType.INTERIOR);
super.setTrailing();
}
/**
* Splits any comments above an open root object (supported formats
* only) into a root header and a first value header.
*
* @param root The root {@link JsonObject} where this data will be
* stored.
*/
protected void readAboveOpenRoot(final JsonObject root) {
this.readWhitespace();
this.splitOpenHeader(root);
}
@Override
protected void readAfter() {
this.readLineWhitespace();
this.setComment(CommentType.EOL);
}
@Override
protected void readBottom() {
this.readWhitespace(false);
this.prependLinesSkippedToComment();
this.setComment(CommentType.FOOTER);
this.expectEndOfText();
}
/**
* Appends the given token to the comment buffer.
*
* @param t Any token representing a comment.
*/
protected void appendComment(final CommentToken t) {
this.commentBuffer.append(new Comment(t));
}
/**
* For each line skipped, appends a newline character to the
* beginning of the comment buffer, resetting the
* counter to 0.
*/
protected void prependLinesSkippedToComment() {
if (this.linesSkipped > 1) {
this.commentBuffer.prepend(this.linesSkipped);
this.linesSkipped = 0;
}
}
/**
* Applies the contents of the comment buffer to the formatting
* output as the given type of comment.
*
* @param type The type of comment being set.
*/
protected void setComment(final CommentType type) {
final CommentData data = this.takeComment(type);
if (!data.isEmpty()) {
this.formatting.getComments().setData(type, data);
}
}
/**
* Returns the full text of the comment buffer, cropping it
* according to the rules defined in {@link CommentType}.
*
* @param type The type of comment currently in the buffer.
* @return The text of the comment buffer.
*/
protected CommentData takeComment(final CommentType type) {
if (this.commentBuffer.isEmpty()) {
return new CommentData();
}
// line comments _must_ have newlines,
// so they will be added later.
if (this.commentBuffer.endsWithNewline()) {
if (this.shouldTakeNl(type)) {
this.trimComment();
this.linesSkipped++;
} else if (this.shouldTrimNl(type)) {
this.trimComment();
}
}
final CommentData taken = this.commentBuffer;
this.commentBuffer = new CommentData();
return taken;
}
// header comments always include a newline
protected final boolean shouldTrimNl(final CommentType type) {
return type == CommentType.HEADER;
}
// eol comments go to eol, but do not include the newline
protected final boolean shouldTakeNl(final CommentType type) {
return type == CommentType.EOL;
}
/**
* Removes the last character (usually a newline character) from the
* comment buffer.
*/
protected final void trimComment() {
this.commentBuffer.trimLastNewline();
}
/**
* Splits the contents of the comment buffer into a header of the
* root JSON object and one of the first member in the object.
*
* For example, assume the following comments:
*
*
* // first
* // second
*
* // third
*
* // fourth
* key: value
*
*
* This method pairs the first, second, and third comment with
* the root object, and the fourth comment with the key-value pair.
*
* @param root The root {@link JsonObject} where the data will be
* stored.
*/
protected void splitOpenHeader(final JsonObject root) {
final CommentData header = this.commentBuffer.takeOpenHeader();
if (header != null) {
root.getComments().setData(CommentType.HEADER, header);
root.setLinesAbove(this.linesSkipped);
this.linesSkipped = header.takeLastLinesSkipped() - 1;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy