diff options
author | Omer Azmon <omer_azmon@intuit.com> | 2020-10-13 19:57:50 -0700 |
---|---|---|
committer | Omer Azmon <omer_azmon@intuit.com> | 2020-10-13 19:57:50 -0700 |
commit | ec339568abd68994804d763327a05b29f88002fd (patch) | |
tree | 6f974cfc068f40045d49f5c4c06d07bb97e50c5c | |
parent | dea399176c2d1fee919cb671600d2b244c94f503 (diff) | |
download | snakeyaml-ec339568abd68994804d763327a05b29f88002fd.tar.gz |
implement serializer and emitter comment changes required changes to
prior commits
20 files changed, 2060 insertions, 199 deletions
diff --git a/src/main/java/org/yaml/snakeyaml/LoaderOptions.java b/src/main/java/org/yaml/snakeyaml/LoaderOptions.java index b23bbee0..724893cb 100644 --- a/src/main/java/org/yaml/snakeyaml/LoaderOptions.java +++ b/src/main/java/org/yaml/snakeyaml/LoaderOptions.java @@ -21,6 +21,7 @@ public class LoaderOptions { private boolean wrappedToRootException = false; private int maxAliasesForCollections = 50; //to prevent YAML at https://en.wikipedia.org/wiki/Billion_laughs_attack private boolean allowRecursiveKeys = false; + private boolean processComments = false; public boolean isAllowDuplicateKeys() { return allowDuplicateKeys; @@ -85,4 +86,17 @@ public class LoaderOptions { public boolean getAllowRecursiveKeys() { return allowRecursiveKeys; } + + /** + * Set the comment processing. By default comments are ignored. + * + * @param processComments <code>true</code> to process; <code>false</code> to ignore</code> + */ + public void setProcessComments(boolean processComments) { + this.processComments = processComments; + } + + public boolean isProcessComments() { + return processComments; + } } diff --git a/src/main/java/org/yaml/snakeyaml/Yaml.java b/src/main/java/org/yaml/snakeyaml/Yaml.java index 64f68a25..e52bb6e5 100644 --- a/src/main/java/org/yaml/snakeyaml/Yaml.java +++ b/src/main/java/org/yaml/snakeyaml/Yaml.java @@ -568,7 +568,8 @@ public class Yaml { * Overview</a> */ public Node compose(Reader yaml) { - Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver, loadingConfig); + Composer composer = new Composer(new ParserImpl(new StreamReader(yaml), + loadingConfig.isProcessComments()), resolver, loadingConfig); return composer.getSingleNode(); } @@ -581,7 +582,8 @@ public class Yaml { * @see <a href="http://yaml.org/spec/1.1/#id859333">Processing Overview</a> */ public Iterable<Node> composeAll(Reader yaml) { - final Composer composer = new Composer(new ParserImpl(new StreamReader(yaml)), resolver, loadingConfig); + final Composer composer = new Composer(new ParserImpl(new StreamReader(yaml), + loadingConfig.isProcessComments()), resolver, loadingConfig); Iterator<Node> result = new Iterator<Node>() { @Override public boolean hasNext() { diff --git a/src/main/java/org/yaml/snakeyaml/comments/CommentEventsCollector.java b/src/main/java/org/yaml/snakeyaml/comments/CommentEventsCollector.java new file mode 100644 index 00000000..b5ed5349 --- /dev/null +++ b/src/main/java/org/yaml/snakeyaml/comments/CommentEventsCollector.java @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2020, http://www.snakeyaml.org + * + * 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.comments; + +import java.util.AbstractQueue; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; + +import org.yaml.snakeyaml.events.CommentEvent; +import org.yaml.snakeyaml.events.Event; +import org.yaml.snakeyaml.parser.Parser; + +/** + * Used by the Composer and Emitter to collect comment events so that they can be used at a later point in the process. + */ +public class CommentEventsCollector { + private List<CommentLine> commentLineList; + private Queue<Event> eventSource; + private CommentType[] expectedCommentTypes; + + /** + * Constructor used to collect comment events emitted by a Parser. + * + * @param parser + * the event source. + * @param expectedCommentTypes + * the comment types expected. Any comment types not included are not collected. + */ + public CommentEventsCollector(final Parser parser, CommentType... expectedCommentTypes) { + this.eventSource = new AbstractQueue<Event>() { + + @Override + public boolean offer(Event e) { + throw new UnsupportedOperationException(); + } + + @Override + public Event poll() { + return parser.getEvent(); + } + + @Override + public Event peek() { + return parser.peekEvent(); + } + + @Override + public Iterator<Event> iterator() { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + throw new UnsupportedOperationException(); + } + + }; + this.expectedCommentTypes = expectedCommentTypes; + commentLineList = new ArrayList<>(); + } + + /** + * Constructor used to collect events emitted by the Serializer. + * + * @param eventSource + * the event source. + * + * @param expectedCommentTypes + * the comment types expected. Any comment types not included are not collected. + */ + public CommentEventsCollector(Queue<Event> eventSource, CommentType... expectedCommentTypes) { + this.eventSource = eventSource; + this.expectedCommentTypes = expectedCommentTypes; + commentLineList = new ArrayList<>(); + } + + /** + * Determine if the event is a comment of one of the expected types set during construction. + * + * @param event + * the event to test. + * @return <code>true</code> if the events is a comment of the expected type; Otherwise, false. + */ + private boolean isEventExpected(Event event) { + if (event == null || !event.is(Event.ID.Comment)) { + return false; + } + CommentEvent commentEvent = (CommentEvent) event; + for (CommentType type : expectedCommentTypes) { + if (commentEvent.getCommentType() == type) { + return true; + } + } + return false; + } + + /** + * Collect all events of the expected type (set during construction) starting with the top event on the event source. + * Collection stops as soon as a non comment or comment of the unexpected type is encountered. + * + * @return this object. + */ + public CommentEventsCollector collectEvents() { + collectEvents(null); + return this; + } + + /** + * Collect all events of the expected type (set during construction) starting with event provided as an argument and + * continuing with the top event on the event source. Collection stops as soon as a non comment or comment of the + * unexpected type is encountered. + * + * @param event + * the first event to attempt to collect. + * @return the event provided as an argument, if it is not collected; Otherwise, <code>null</code> + */ + public Event collectEvents(Event event) { + if (event != null) { + if (isEventExpected(event)) { + commentLineList.add(new CommentLine((CommentEvent) event)); + } else { + return event; + } + } + while (isEventExpected(eventSource.peek())) { + commentLineList.add(new CommentLine((CommentEvent) eventSource.poll())); + } + return null; + } + + /** + * Collect all events of the expected type (set during construction) starting with event provided as an argument and + * continuing with the top event on the event source. Collection stops as soon as a non comment or comment of the + * unexpected type is encountered. + * + * @param event + * the first event to attempt to collect. + * @return the event provided as an argument, if it is not collected; Otherwise, the first event that is not collected. + */ + public Event collectEventsAndPoll(Event event) { + Event nextEvent = collectEvents(event); + return nextEvent != null ? nextEvent : eventSource.poll(); + } + + /** + * Return the events collected and reset the colletor. + * + * @return the events collected. + */ + public List<CommentLine> consume() { + try { + return commentLineList; + } finally { + commentLineList = new ArrayList<>(); + } + } + + /** + * Test if the collector contains any collected events. + * @return <code>true</code> if it does; Otherwise, <code>false</code> + */ + public boolean isEmpty() { + return commentLineList.isEmpty(); + } +} diff --git a/src/main/java/org/yaml/snakeyaml/comments/CommentLine.java b/src/main/java/org/yaml/snakeyaml/comments/CommentLine.java new file mode 100644 index 00000000..63732cc0 --- /dev/null +++ b/src/main/java/org/yaml/snakeyaml/comments/CommentLine.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2020, http://www.snakeyaml.org + * + * 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.comments; + +import org.yaml.snakeyaml.error.Mark; +import org.yaml.snakeyaml.events.CommentEvent; + +/** + * A comment line. May be a block comment, blank line, or inline comment. + */ +public class CommentLine { + private Mark startMark; + private Mark endMark; + private String value; + private CommentType commentType; + + public CommentLine(CommentEvent event) { + this(event.getStartMark(), event.getEndMark(), event.getValue(), event.getCommentType()); + } + + public CommentLine(Mark startMark, Mark endMark, String value, CommentType commentType) { + this.startMark = startMark; + this.endMark = endMark; + this.value = value; + this.commentType = commentType; + } + + /** + * CommentLine is only equal to itself + */ + @Override + public final boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public final int hashCode() { + return super.hashCode(); + } + + public Mark getEndMark() { + return endMark; + } + + public Mark getStartMark() { + return startMark; + } + + /** + * Is this comment blank lines or a regular comment (starts with '#'). + * + * @return <code>true</code> if blank lines; Otherwise, <code>false</code>. + */ + public boolean isBlankLines() { + return commentType == CommentType.BLANK_LINE; + } + + public CommentType getCommentType() { + return commentType; + } + + /** + * Value of this comment. + * + * @return comment's value. + */ + public String getValue() { + return value; + } + + public String toString() { + return "<" + this.getClass().getName() + " (type=" + getCommentType() + ", value=" + getValue() + ")>"; + } +} diff --git a/src/main/java/org/yaml/snakeyaml/comments/CommentType.java b/src/main/java/org/yaml/snakeyaml/comments/CommentType.java new file mode 100644 index 00000000..2abc9a88 --- /dev/null +++ b/src/main/java/org/yaml/snakeyaml/comments/CommentType.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2020, http://www.snakeyaml.org + * + * 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.comments; + +/** + * The type of a comment line. + */ +public enum CommentType { + BLANK_LINE, // + BLOCK, // + IN_LINE; // +}
\ No newline at end of file diff --git a/src/main/java/org/yaml/snakeyaml/composer/Composer.java b/src/main/java/org/yaml/snakeyaml/composer/Composer.java index 964391ac..2135d84f 100644 --- a/src/main/java/org/yaml/snakeyaml/composer/Composer.java +++ b/src/main/java/org/yaml/snakeyaml/composer/Composer.java @@ -16,13 +16,18 @@ package org.yaml.snakeyaml.composer; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.comments.CommentEventsCollector; +import org.yaml.snakeyaml.comments.CommentLine; +import org.yaml.snakeyaml.comments.CommentType; import org.yaml.snakeyaml.error.Mark; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.events.AliasEvent; @@ -55,6 +60,8 @@ public class Composer { private final Set<Node> recursiveNodes; private int nonScalarAliasesCount = 0; private final LoaderOptions loadingConfig; + private final CommentEventsCollector blockCommentsCollector; + private final CommentEventsCollector inlineCommentsCollector; public Composer(Parser parser, Resolver resolver) { this(parser, resolver, new LoaderOptions()); @@ -66,6 +73,10 @@ public class Composer { this.anchors = new HashMap<String, Node>(); this.recursiveNodes = new HashSet<Node>(); this.loadingConfig = loadingConfig; + this.blockCommentsCollector = new CommentEventsCollector(parser, + CommentType.BLANK_LINE, CommentType.BLOCK); + this.inlineCommentsCollector = new CommentEventsCollector(parser, + CommentType.IN_LINE); } /** @@ -85,17 +96,29 @@ public class Composer { /** * Reads and composes the next document. * - * @return The root node of the document or <code>null</code> if no more - * documents are available. + * @return The root node of the document or <code>null</code> if no more documents are available. */ public Node getNode() { + // Collect inter-document start comments + blockCommentsCollector.collectEvents(); + if (parser.checkEvent(Event.ID.StreamEnd)) { + List<CommentLine> commentLines = blockCommentsCollector.consume(); + Mark startMark = commentLines.get(0).getStartMark(); + List<NodeTuple> children = Collections.emptyList(); + Node node = new MappingNode(Tag.COMMENT, false, children, startMark, null, FlowStyle.BLOCK); + node.setBlockComments(commentLines); + return node; + } // Drop the DOCUMENT-START event. parser.getEvent(); // Compose the root node. - Node node = composeNode(null); + Node node = composeNode(null, blockCommentsCollector.collectEvents().consume()); // Drop the DOCUMENT-END event. + blockCommentsCollector.collectEvents(); + if(!blockCommentsCollector.isEmpty()) { + node.setEndComments(blockCommentsCollector.consume()); + } parser.getEvent(); - //clean up resources this.anchors.clear(); this.recursiveNodes.clear(); return node; @@ -113,11 +136,19 @@ public class Composer { public Node getSingleNode() { // Drop the STREAM-START event. parser.getEvent(); + // Drop any leading comments (though should not have run this case with comments on) + while (parser.checkEvent(Event.ID.Comment)) { + parser.getEvent(); + } // Compose a document if the stream is not empty. Node document = null; if (!parser.checkEvent(Event.ID.StreamEnd)) { document = getNode(); } + // Drop any trailing comments (though should not have run this case with comments on) + while (parser.checkEvent(Event.ID.Comment)) { + parser.getEvent(); + } // Ensure that the stream contains no more documents. if (!parser.checkEvent(Event.ID.StreamEnd)) { Event event = parser.getEvent(); @@ -130,8 +161,9 @@ public class Composer { return document; } - private Node composeNode(Node parent) { - if (parent != null) recursiveNodes.add(parent); + private Node composeNode(Node parent, List<CommentLine> blockComments) { + if (parent != null) + recursiveNodes.add(parent); final Node node; if (parser.checkEvent(Event.ID.Alias)) { AliasEvent event = (AliasEvent) parser.getEvent(); @@ -150,23 +182,24 @@ public class Composer { if (recursiveNodes.remove(node)) { node.setTwoStepsConstruction(true); } + node.setBlockComments(blockComments); } else { NodeEvent event = (NodeEvent) parser.peekEvent(); String anchor = event.getAnchor(); // the check for duplicate anchors has been removed (issue 174) if (parser.checkEvent(Event.ID.Scalar)) { - node = composeScalarNode(anchor); + node = composeScalarNode(anchor, blockComments); } else if (parser.checkEvent(Event.ID.SequenceStart)) { - node = composeSequenceNode(anchor); + node = composeSequenceNode(anchor, blockComments); } else { - node = composeMappingNode(anchor); + node = composeMappingNode(anchor, blockComments); } } recursiveNodes.remove(parent); return node; } - protected Node composeScalarNode(String anchor) { + protected Node composeScalarNode(String anchor, List<CommentLine> blockComments) { ScalarEvent ev = (ScalarEvent) parser.getEvent(); String tag = ev.getTag(); boolean resolved = false; @@ -184,13 +217,16 @@ public class Composer { node.setAnchor(anchor); anchors.put(anchor, node); } + node.setBlockComments(blockComments); + node.setInLineComments(inlineCommentsCollector.collectEvents().consume()); return node; } - protected Node composeSequenceNode(String anchor) { + protected Node composeSequenceNode(String anchor, List<CommentLine> blockComments) { SequenceStartEvent startEvent = (SequenceStartEvent) parser.getEvent(); String tag = startEvent.getTag(); Tag nodeTag; + boolean resolved = false; if (tag == null || tag.equals("!")) { nodeTag = resolver.resolve(NodeId.sequence, null, startEvent.getImplicit()); @@ -205,15 +241,25 @@ public class Composer { node.setAnchor(anchor); anchors.put(anchor, node); } + node.setBlockComments(blockComments); + node.setInLineComments(inlineCommentsCollector.collectEvents().consume()); while (!parser.checkEvent(Event.ID.SequenceEnd)) { - children.add(composeNode(node)); + blockCommentsCollector.collectEvents(); + if (parser.checkEvent(Event.ID.SequenceEnd)) { + break; + } + children.add(composeNode(node, blockCommentsCollector.consume())); } Event endEvent = parser.getEvent(); node.setEndMark(endEvent.getEndMark()); + inlineCommentsCollector.collectEvents(); + if(!inlineCommentsCollector.isEmpty()) { + node.setInLineComments(inlineCommentsCollector.consume()); + } return node; } - protected Node composeMappingNode(String anchor) { + protected Node composeMappingNode(String anchor, List<CommentLine> blockComments) { MappingStartEvent startEvent = (MappingStartEvent) parser.getEvent(); String tag = startEvent.getTag(); Tag nodeTag; @@ -232,28 +278,38 @@ public class Composer { node.setAnchor(anchor); anchors.put(anchor, node); } + node.setBlockComments(blockComments); + node.setInLineComments(inlineCommentsCollector.collectEvents().consume()); while (!parser.checkEvent(Event.ID.MappingEnd)) { - composeMappingChildren(children, node); + blockCommentsCollector.collectEvents(); + if (parser.checkEvent(Event.ID.MappingEnd)) { + break; + } + composeMappingChildren(children, node, blockCommentsCollector.consume()); } Event endEvent = parser.getEvent(); node.setEndMark(endEvent.getEndMark()); + inlineCommentsCollector.collectEvents(); + if(!inlineCommentsCollector.isEmpty()) { + node.setInLineComments(inlineCommentsCollector.consume()); + } return node; } - protected void composeMappingChildren(List<NodeTuple> children, MappingNode node) { - Node itemKey = composeKeyNode(node); + protected void composeMappingChildren(List<NodeTuple> children, MappingNode node, List<CommentLine> keyBlockComments) { + Node itemKey = composeKeyNode(node, keyBlockComments); if (itemKey.getTag().equals(Tag.MERGE)) { node.setMerged(true); } - Node itemValue = composeValueNode(node); + Node itemValue = composeValueNode(node, blockCommentsCollector.collectEvents().consume()); children.add(new NodeTuple(itemKey, itemValue)); } - protected Node composeKeyNode(MappingNode node) { - return composeNode(node); + protected Node composeKeyNode(MappingNode node, List<CommentLine> blockComments) { + return composeNode(node, blockComments); } - protected Node composeValueNode(MappingNode node) { - return composeNode(node); + protected Node composeValueNode(MappingNode node, List<CommentLine> blockComments) { + return composeNode(node, blockComments); } } diff --git a/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java index 4caad750..c060a7cb 100644 --- a/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java +++ b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java @@ -16,14 +16,20 @@ package org.yaml.snakeyaml.emitter; 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; @@ -43,6 +49,7 @@ 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; @@ -165,6 +172,11 @@ public final class Emitter implements Emitable { // Scalar analysis and style. private ScalarAnalysis analysis; private DumperOptions.ScalarStyle style; + + // Comment processing + private final CommentEventsCollector blockCommentsCollector; + private final CommentEventsCollector inlineCommentsCollector; + public Emitter(Writer stream, DumperOptions opts) { // The stream should have the methods `write` and possibly `flush`. @@ -226,6 +238,12 @@ public final class Emitter implements Emitable { // 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 { @@ -243,36 +261,53 @@ public final class Emitter implements Emitable { if (events.isEmpty()) { return true; } - Event event = events.peek(); - 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; + Iterator<Event> iter = events.iterator(); + Event event = null; + while(iter.hasNext()) { + event = iter.next(); + if (event instanceof CommentEvent) { + continue; + } + 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 { + // To collect any comment events + return needEvents(iter, 1); + } } + return true; } - - private boolean needEvents(int count) { + + private boolean needEvents(Iterator<Event> iter, int count) { int level = 0; - Iterator<Event> iter = events.iterator(); - iter.next(); + 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; + } else if (event instanceof CommentEvent) { } if (level < 0) { return false; } } - return events.size() < count + 1; + return actualCount < count; } private void increaseIndent(boolean flow, boolean indentless) { @@ -366,6 +401,10 @@ public final class Emitter implements Emitable { // } 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); } @@ -374,6 +413,8 @@ public final class Emitter implements Emitable { private class ExpectDocumentEnd implements EmitterState { public void expect() throws IOException { + event = blockCommentsCollector.collectEventsAndPoll(event); + writeBlockComment(); if (event instanceof DocumentEndEvent) { writeIndent(); if (((DocumentEndEvent) event).getExplicit()) { @@ -390,6 +431,14 @@ public final class Emitter implements Emitable { 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); } @@ -461,13 +510,20 @@ public final class Emitter implements Emitable { 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(); } } } @@ -482,10 +538,15 @@ public final class Emitter implements Emitable { writeIndent(); } writeIndicator("]", false, false, false); + inlineCommentsCollector.collectEvents(); + writeInlineComments(); if (prettyFlow) { writeIndent(); } state = states.pop(); + } else if (event instanceof CommentEvent) { + blockCommentsCollector.collectEvents(event); + writeBlockComment(); } else { writeIndicator(",", false, false, false); if (canonical || (column > bestWidth && splitLines) || prettyFlow) { @@ -493,6 +554,8 @@ public final class Emitter implements Emitable { } states.push(new ExpectFlowSequenceItem()); expectNode(false, false, false); + inlineCommentsCollector.collectEvents(event); + writeInlineComments(); } } } @@ -515,6 +578,8 @@ public final class Emitter implements Emitable { indent = indents.pop(); flowLevel--; writeIndicator("}", false, false, false); + inlineCommentsCollector.collectEvents(); + writeInlineComments(); state = states.pop(); } else { if (canonical || (column > bestWidth && splitLines) || prettyFlow) { @@ -545,6 +610,8 @@ public final class Emitter implements Emitable { writeIndent(); } writeIndicator("}", false, false, false); + inlineCommentsCollector.collectEvents(); + writeInlineComments(); state = states.pop(); } else { writeIndicator(",", false, false, false); @@ -566,8 +633,12 @@ public final class Emitter implements Emitable { 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(); } } @@ -577,8 +648,12 @@ public final class Emitter implements Emitable { writeIndent(); } writeIndicator(":", true, false, false); + event = inlineCommentsCollector.collectEventsAndPoll(event); + writeInlineComments(); states.push(new ExpectFlowMappingKey()); expectNode(false, true, false); + inlineCommentsCollector.collectEvents(event); + writeInlineComments(); } } @@ -607,6 +682,9 @@ public final class Emitter implements Emitable { if (!this.first && event instanceof SequenceEndEvent) { indent = indents.pop(); state = states.pop(); + } else if( event instanceof CommentEvent) { + blockCommentsCollector.collectEvents(event); + writeBlockComment(); } else { writeIndent(); if (!indentWithIndicator || this.first) { @@ -618,6 +696,8 @@ public final class Emitter implements Emitable { } states.push(new ExpectBlockSequenceItem(false)); expectNode(false, false, false); + inlineCommentsCollector.collectEvents(); + writeInlineComments(); } } } @@ -642,6 +722,8 @@ public final class Emitter implements Emitable { } public void expect() throws IOException { + event = blockCommentsCollector.collectEventsAndPoll(event); + writeBlockComment(); if (!this.first && event instanceof MappingEndEvent) { indent = indents.pop(); state = states.pop(); @@ -659,11 +741,37 @@ public final class Emitter implements Emitable { } } + 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(); } } @@ -671,8 +779,14 @@ public final class Emitter implements Emitable { 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(); } } @@ -1316,6 +1430,39 @@ public final class Emitter implements Emitable { } writeIndicator("\"", false, false, false); } + + private boolean writeCommentLines(List<CommentLine> commentLines) throws IOException { + int indentColumns = 0; + boolean firstComment = true; + boolean wroteComment = false; + 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); + 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(); @@ -1337,7 +1484,9 @@ public final class Emitter implements Emitable { if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) { openEnded = true; } - writeLineBreak(null); + if(!writeInlineComments()) { + writeLineBreak(null); + } boolean leadingSpace = true; boolean spaces = false; boolean breaks = true; @@ -1402,7 +1551,9 @@ public final class Emitter implements Emitable { if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') { openEnded = true; } - writeLineBreak(null); + if(!writeInlineComments()) { + writeLineBreak(null); + } boolean breaks = true; int start = 0, end = 0; while (end <= text.length()) { diff --git a/src/main/java/org/yaml/snakeyaml/events/CommentEvent.java b/src/main/java/org/yaml/snakeyaml/events/CommentEvent.java index 4ba5e9e7..30236f94 100644 --- a/src/main/java/org/yaml/snakeyaml/events/CommentEvent.java +++ b/src/main/java/org/yaml/snakeyaml/events/CommentEvent.java @@ -15,18 +15,13 @@ */ package org.yaml.snakeyaml.events; -import org.yaml.snakeyaml.error.Mark;import org.yaml.snakeyaml.tokens.Token; +import org.yaml.snakeyaml.comments.CommentType; +import org.yaml.snakeyaml.error.Mark; /** - * Marks a scalar value. + * Marks a comment block value. */ -public final class CommentEvent extends Event { - public static enum CommentType { - BLANK_LINE, // - BLOCK, // - IN_LINE // - } - +public final class CommentEvent extends Event { private final CommentType type; private final String value; @@ -44,7 +39,7 @@ public final class CommentEvent extends Event { * Without quotes and escaping. * </p> * - * @return Value as Unicode string. + * @return Value a comment line string without the leading '#' or a blank line. */ public String getValue() { return this.value; @@ -61,7 +56,7 @@ public final class CommentEvent extends Event { @Override protected String getArguments() { - return super.getArguments() + ", value=" + value; + return super.getArguments() + "type=" + type + ", value=" + value; } @Override diff --git a/src/main/java/org/yaml/snakeyaml/nodes/Node.java b/src/main/java/org/yaml/snakeyaml/nodes/Node.java index d3088fc0..fa5030b0 100644 --- a/src/main/java/org/yaml/snakeyaml/nodes/Node.java +++ b/src/main/java/org/yaml/snakeyaml/nodes/Node.java @@ -15,19 +15,20 @@ */ package org.yaml.snakeyaml.nodes; +import java.util.List; + +import org.yaml.snakeyaml.comments.CommentLine; import org.yaml.snakeyaml.error.Mark; /** * Base class for all nodes. * <p> - * The nodes form the node-graph described in the <a - * href="http://yaml.org/spec/1.1/">YAML Specification</a>. + * The nodes form the node-graph described in the <a href="http://yaml.org/spec/1.1/">YAML Specification</a>. * </p> * <p> - * While loading, the node graph is usually created by the - * {@link org.yaml.snakeyaml.composer.Composer}, and later transformed into - * application specific Java classes by the classes from the - * {@link org.yaml.snakeyaml.constructor} package. + * While loading, the node graph is usually created by the {@link org.yaml.snakeyaml.composer.Composer}, and later + * transformed into application specific Java classes by the classes from the {@link org.yaml.snakeyaml.constructor} + * package. * </p> */ public abstract class Node { @@ -37,6 +38,10 @@ public abstract class Node { private Class<? extends Object> type; private boolean twoStepsConstruction; private String anchor; + private List<CommentLine> inLineComments; + private List<CommentLine> blockComments; + // End Comments are only on the last node in a document + private List<CommentLine> endComments; /** * true when the tag is assigned by the resolver @@ -52,6 +57,9 @@ public abstract class Node { this.twoStepsConstruction = false; this.resolved = true; this.useClassConstructor = null; + this.inLineComments = null; + this.blockComments = null; + this.endComments = null; } /** @@ -113,13 +121,11 @@ public abstract class Node { /** * Indicates if this node must be constructed in two steps. * <p> - * Two-step construction is required whenever a node is a child (direct or - * indirect) of it self. That is, if a recursive structure is build using - * anchors and aliases. + * Two-step construction is required whenever a node is a child (direct or indirect) of it self. That is, if a recursive + * structure is build using anchors and aliases. * </p> * <p> - * Set by {@link org.yaml.snakeyaml.composer.Composer}, used during the - * construction process. + * Set by {@link org.yaml.snakeyaml.composer.Composer}, used during the construction process. * </p> * <p> * Only relevant during loading. @@ -138,8 +144,7 @@ public abstract class Node { public boolean useClassConstructor() { if (useClassConstructor == null) { - if (!tag.isSecondary() && resolved && !Object.class.equals(type) - && !tag.equals(Tag.NULL)) { + if (!tag.isSecondary() && resolved && !Object.class.equals(type) && !tag.equals(Tag.NULL)) { return true; } else if (tag.isCompatible(getType())) { // the tag is compatible with the runtime class @@ -157,12 +162,12 @@ public abstract class Node { } /** - * Indicates if the tag was added by - * {@link org.yaml.snakeyaml.resolver.Resolver}. + * Indicates if the tag was added by {@link org.yaml.snakeyaml.resolver.Resolver}. * * @return true if the tag of this node was resolved * - * @deprecated Since v1.22. Absent in immediately prior versions, but present previously. Restored deprecated for backwards compatibility. + * @deprecated Since v1.22. Absent in immediately prior versions, but present previously. Restored deprecated for + * backwards compatibility. */ @Deprecated public boolean isResolved() { @@ -176,4 +181,46 @@ public abstract class Node { public void setAnchor(String anchor) { this.anchor = anchor; } + + /** + * The ordered list of in-line comments. The first of which appears at the end of the line respresent by this node. + * The rest are in the following lines, indented per the Spec to indicate they are continuation of the inline comment. + * + * @return the comment line list. + */ + public List<CommentLine> getInLineComments() { + return inLineComments; + } + + public void setInLineComments(List<CommentLine> inLineComments) { + this.inLineComments = inLineComments; + } + + /** + * The ordered list of blank lines and block comments (full line) that appear before this node. + * + * @return the comment line list. + */ + public List<CommentLine> getBlockComments() { + return blockComments; + } + + public void setBlockComments(List<CommentLine> blockComments) { + this.blockComments = blockComments; + } + + /** + * The ordered list of blank lines and block comments (full line) that appear AFTER this node. + * <p> + * NOTE: these comment should occur only in the last node in a document, when walking the node tree "in order" + * + * @return the comment line list. + */ + public List<CommentLine> getEndComments() { + return endComments; + } + + public void setEndComments(List<CommentLine> endComments) { + this.endComments = endComments; + } } diff --git a/src/main/java/org/yaml/snakeyaml/nodes/Tag.java b/src/main/java/org/yaml/snakeyaml/nodes/Tag.java index 3f299000..0c09a018 100644 --- a/src/main/java/org/yaml/snakeyaml/nodes/Tag.java +++ b/src/main/java/org/yaml/snakeyaml/nodes/Tag.java @@ -43,6 +43,8 @@ public final class Tag { public static final Tag STR = new Tag(PREFIX + "str"); public static final Tag SEQ = new Tag(PREFIX + "seq"); public static final Tag MAP = new Tag(PREFIX + "map"); + // For use to indicate a DUMMY node that contains comments, when there is no other (empty document) + public static final Tag COMMENT = new Tag(PREFIX + "comment"); protected static final Map<Tag, Set<Class<?>>> COMPATIBILITY_MAP; static { COMPATIBILITY_MAP = new HashMap<Tag, Set<Class<?>>>(); @@ -96,7 +98,8 @@ public final class Tag { /** * @deprecated - it will be removed - * @param uri - URI to be encoded as tag value + * @param uri + * - URI to be encoded as tag value */ public Tag(URI uri) { if (uri == null) { @@ -143,13 +146,11 @@ public final class Tag { } /** - * Java has more then 1 class compatible with a language-independent tag - * (!!int, !!float, !!timestamp etc) + * Java has more then 1 class compatible with a language-independent tag (!!int, !!float, !!timestamp etc) * * @param clazz * - Class to check compatibility - * @return true when the Class can be represented by this - * language-independent tag + * @return true when the Class can be represented by this language-independent tag */ public boolean isCompatible(Class<?> clazz) { Set<Class<?>> set = COMPATIBILITY_MAP.get(this); diff --git a/src/main/java/org/yaml/snakeyaml/parser/ParserImpl.java b/src/main/java/org/yaml/snakeyaml/parser/ParserImpl.java index 1b55bb8c..56a9c993 100644 --- a/src/main/java/org/yaml/snakeyaml/parser/ParserImpl.java +++ b/src/main/java/org/yaml/snakeyaml/parser/ParserImpl.java @@ -21,11 +21,11 @@ import java.util.Map; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions.Version; +import org.yaml.snakeyaml.comments.CommentType; import org.yaml.snakeyaml.error.Mark; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.events.AliasEvent; import org.yaml.snakeyaml.events.CommentEvent; -import org.yaml.snakeyaml.events.CommentEvent.CommentType; import org.yaml.snakeyaml.events.DocumentEndEvent; import org.yaml.snakeyaml.events.DocumentStartEvent; import org.yaml.snakeyaml.events.Event; @@ -182,7 +182,7 @@ public class ParserImpl implements Parser { Mark startMark = token.getStartMark(); Mark endMark = token.getEndMark(); String value = token.getValue(); - CommentType type = CommentType.valueOf(token.getCommentType().name()); + CommentType type = token.getCommentType(); // state = state, that no change in state @@ -282,9 +282,6 @@ public class ParserImpl implements Parser { private class ParseDocumentEnd implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } // Parse the document end. Token token = scanner.peekToken(); Mark startMark = token.getStartMark(); @@ -395,9 +392,6 @@ public class ParserImpl implements Parser { private class ParseBlockNode implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } return parseNode(true, false); } } @@ -536,9 +530,6 @@ public class ParserImpl implements Parser { private class ParseBlockSequenceFirstEntry implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } Token token = scanner.getToken(); marks.push(token.getStartMark()); return new ParseBlockSequenceEntry().produce(); @@ -601,9 +592,6 @@ public class ParserImpl implements Parser { private class ParseBlockMappingFirstKey implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } Token token = scanner.getToken(); marks.push(token.getStartMark()); return new ParseBlockMappingKey().produce(); @@ -641,13 +629,11 @@ public class ParserImpl implements Parser { private class ParseBlockMappingValue implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } if (scanner.checkToken(Token.ID.Value)) { Token token = scanner.getToken(); if (scanner.checkToken(Token.ID.Comment)) { - return parseBlockNodeOrIndentlessSequence(); + state = new ParseBlockMappingValueComment(); + return state.produce(); } else if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) { states.push(new ParseBlockMappingKey()); return parseBlockNodeOrIndentlessSequence(); @@ -665,6 +651,21 @@ public class ParserImpl implements Parser { } } + private class ParseBlockMappingValueComment implements Production { + public Event produce() { + if (scanner.checkToken(Token.ID.Comment)) { + return parseBlockNodeOrIndentlessSequence(); + } else if (!scanner.checkToken(Token.ID.Key, Token.ID.Value, Token.ID.BlockEnd)) { + states.push(new ParseBlockMappingKey()); + return parseBlockNodeOrIndentlessSequence(); + } else { + state = new ParseBlockMappingKey(); + Token token = scanner.getToken(); + return processEmptyScalar(token.getEndMark()); + } + } + } + /** * <pre> * flow_sequence ::= FLOW-SEQUENCE-START @@ -680,9 +681,6 @@ public class ParserImpl implements Parser { */ private class ParseFlowSequenceFirstEntry implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } Token token = scanner.getToken(); marks.push(token.getStartMark()); return new ParseFlowSequenceEntry(true).produce(); @@ -697,9 +695,6 @@ public class ParserImpl implements Parser { } public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } if (!scanner.checkToken(Token.ID.FlowSequenceEnd)) { if (!first) { if (scanner.checkToken(Token.ID.FlowEntry)) { @@ -724,17 +719,27 @@ public class ParserImpl implements Parser { } Token token = scanner.getToken(); Event event = new SequenceEndEvent(token.getStartMark(), token.getEndMark()); - state = states.pop(); marks.pop(); + if(!scanner.checkToken(Token.ID.Comment)) { + state = states.pop(); + } else { + state = new ParseFlowEndComment(); + } return event; } } - private class ParseFlowSequenceEntryMappingKey implements Production { + private class ParseFlowEndComment implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); + Event event = produceCommentEvent((CommentToken)scanner.getToken()); + if(!scanner.checkToken(Token.ID.Comment)) { + state = states.pop(); } + return event; + } + } + private class ParseFlowSequenceEntryMappingKey implements Production { + public Event produce() { Token token = scanner.getToken(); if (!scanner.checkToken(Token.ID.Value, Token.ID.FlowEntry, Token.ID.FlowSequenceEnd)) { states.push(new ParseFlowSequenceEntryMappingValue()); @@ -748,9 +753,6 @@ public class ParserImpl implements Parser { private class ParseFlowSequenceEntryMappingValue implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } if (scanner.checkToken(Token.ID.Value)) { Token token = scanner.getToken(); if (!scanner.checkToken(Token.ID.FlowEntry, Token.ID.FlowSequenceEnd)) { @@ -770,9 +772,6 @@ public class ParserImpl implements Parser { private class ParseFlowSequenceEntryMappingEnd implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } state = new ParseFlowSequenceEntry(false); Token token = scanner.peekToken(); return new MappingEndEvent(token.getStartMark(), token.getEndMark()); @@ -790,9 +789,6 @@ public class ParserImpl implements Parser { */ private class ParseFlowMappingFirstKey implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } Token token = scanner.getToken(); marks.push(token.getStartMark()); return new ParseFlowMappingKey(true).produce(); @@ -835,17 +831,18 @@ public class ParserImpl implements Parser { } Token token = scanner.getToken(); Event event = new MappingEndEvent(token.getStartMark(), token.getEndMark()); - state = states.pop(); marks.pop(); - return event; + if(!scanner.checkToken(Token.ID.Comment)) { + state = states.pop(); + } else { + state = new ParseFlowEndComment(); + } + return event; } } private class ParseFlowMappingValue implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } if (scanner.checkToken(Token.ID.Value)) { Token token = scanner.getToken(); if (!scanner.checkToken(Token.ID.FlowEntry, Token.ID.FlowMappingEnd)) { @@ -865,9 +862,6 @@ public class ParserImpl implements Parser { private class ParseFlowMappingEmptyValue implements Production { public Event produce() { - if (scanner.checkToken(Token.ID.Comment)) { - return produceCommentEvent((CommentToken) scanner.getToken()); - } state = new ParseFlowMappingKey(false); return processEmptyScalar(scanner.peekToken().getStartMark()); } diff --git a/src/main/java/org/yaml/snakeyaml/scanner/ScannerImpl.java b/src/main/java/org/yaml/snakeyaml/scanner/ScannerImpl.java index db48297d..8eb43920 100644 --- a/src/main/java/org/yaml/snakeyaml/scanner/ScannerImpl.java +++ b/src/main/java/org/yaml/snakeyaml/scanner/ScannerImpl.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.regex.Pattern; import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.comments.CommentType; import org.yaml.snakeyaml.error.Mark; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.reader.StreamReader; @@ -36,7 +37,6 @@ import org.yaml.snakeyaml.tokens.BlockEntryToken; import org.yaml.snakeyaml.tokens.BlockMappingStartToken; import org.yaml.snakeyaml.tokens.BlockSequenceStartToken; import org.yaml.snakeyaml.tokens.CommentToken; -import org.yaml.snakeyaml.tokens.CommentToken.CommentType; import org.yaml.snakeyaml.tokens.DirectiveToken; import org.yaml.snakeyaml.tokens.DocumentEndToken; import org.yaml.snakeyaml.tokens.DocumentStartToken; @@ -112,7 +112,8 @@ public final class ScannerImpl implements Scanner { * \UHHHHHHHH : escaped 32-bit Unicode character * </pre> * - * @see <a href="http://yaml.org/spec/1.1/current.html#id872840">5.6. Escape Sequences</a> + * @see <a href="http://yaml.org/spec/1.1/current.html#id872840">5.6. Escape + * Sequences</a> */ public final static Map<Character, Integer> ESCAPE_CODES = new HashMap<Character, Integer>(); @@ -303,12 +304,8 @@ public final class ScannerImpl implements Scanner { * Fetch one or more tokens from the StreamReader. */ private void fetchMoreTokens() { - // Eat whitespaces until we reach the next token. - int startColumn = reader.getColumn(); - List<Token> blankLines = scanToNextToken(); - startColumn = reader.getColumn() == 0 ? 0 : startColumn; - // Process blank lines as comments - fetchBlankLine(blankLines); + // Eat whitespaces and process comments until we reach the next token. + scanToNextToken(); // Remove obsolete possible simple keys. stalePossibleSimpleKeys(); // Compare the current indentation and column. It may add some tokens @@ -322,9 +319,6 @@ public final class ScannerImpl implements Scanner { // Is it the end of stream? fetchStreamEnd(); return; - case '#': - fetchComment(startColumn == 0 ? CommentType.BLOCK : CommentType.IN_LINE); - return; case '%': // Is it a directive? if (checkDirective()) { @@ -625,24 +619,6 @@ public final class ScannerImpl implements Scanner { this.done = true; } - private void fetchBlankLine(List<Token> tokens) { - // See the specification for details. - if (emitComments && tokens != null) { - this.tokens.addAll(tokens); - } - } - - /** - * Fetch a comment (both block and in-line) - * @param type TODO - */ - private void fetchComment(CommentType type) { - Token tok = scanComment(type); - if (emitComments) { - this.tokens.add(tok); - } - } - /** * Fetch a YAML directive. Directives are presentation details that are * interpreted as instructions to the processor. YAML defines two kinds of @@ -1086,8 +1062,8 @@ public final class ScannerImpl implements Scanner { this.allowSimpleKey = false; // Scan and add SCALAR. May change `allow_simple_key`. - List<Token> tok = scanPlain(); - this.tokens.addAll(tok); + Token tok = scanPlain(); + this.tokens.add(tok); } // Checkers. @@ -1216,18 +1192,17 @@ public final class ScannerImpl implements Scanner { * Scanners for block, flow, and plain scalars need to be modified. * </pre> */ - private List<Token> scanToNextToken() { - List<Token> blankLineTokenList = null; - if(reader.getLine() == 0 && reader.getColumn() == 0) { - blankLineTokenList = new ArrayList<Token>(); - } + private void scanToNextToken() { // If there is a byte order mark (BOM) at the beginning of the stream, // forward past it. if (reader.getIndex() == 0 && reader.peek() == 0xFEFF) { reader.forward(); } boolean found = false; + int inlineStartColumn = -1; while (!found) { + Mark startMark = reader.getMark(); + boolean commentSeen = false; int ff = 0; // Peek ahead until we find the first non-space character, then // move forward directly to that character. @@ -1237,17 +1212,35 @@ public final class ScannerImpl implements Scanner { if (ff > 0) { reader.forward(ff); } + // If the character we have skipped forward to is a comment (#), + // then peek ahead until we find the next end of line. YAML + // comments are from a # to the next new-line. We then forward + // past the comment. + if (reader.peek() == '#') { + commentSeen = true; + CommentType type; + if(startMark.getColumn() != 0) { + type = CommentType.IN_LINE; + inlineStartColumn = reader.getColumn(); + } else if(inlineStartColumn == reader.getColumn()) { + type = CommentType.IN_LINE; + } else { + inlineStartColumn = -1; + type = CommentType.BLOCK; + } + CommentToken token = scanComment(type); + if (emitComments) { + this.tokens.add(token); + } + } // If we scanned a line break, then (depending on flow level), // simple keys may be allowed. - Mark startMark = reader.getMark(); String breaks = scanLineBreak(); if (breaks.length() != 0) {// found a line-break - if(emitComments) { - if (blankLineTokenList == null) { - blankLineTokenList = new ArrayList<Token>(); - } else { + if (emitComments && ! commentSeen) { + if (startMark.getColumn() == 0) { Mark endMark = reader.getMark(); - blankLineTokenList.add(new CommentToken(CommentType.BLANK_LINE, breaks, startMark, endMark)); + this.tokens.add(new CommentToken(CommentType.BLANK_LINE, breaks, startMark, endMark)); } } if (this.flowLevel == 0) { @@ -1259,7 +1252,6 @@ public final class ScannerImpl implements Scanner { found = true; } } - return blankLineTokenList; } private CommentToken scanComment(CommentType type) { @@ -1270,8 +1262,8 @@ public final class ScannerImpl implements Scanner { while (Constant.NULL_OR_LINEBR.hasNo(reader.peek(length))) { length++; } - Mark endMark = reader.getMark(); String value = reader.prefixForward(length); + Mark endMark = reader.getMark(); return new CommentToken(type, value, startMark, endMark); } @@ -1699,7 +1691,7 @@ public final class ScannerImpl implements Scanner { } CommentToken blankLineCommentToken = null; if (chompi.chompTailIsTrue()) { - if(emitComments) { + if (emitComments) { blankLineCommentToken = new CommentToken(CommentType.BLANK_LINE, breaks, startMark, endMark); } chunks.append(breaks); @@ -2044,15 +2036,12 @@ public final class ScannerImpl implements Scanner { * Indentation rules are loosed for the flow context. * </pre> */ - private List<Token> scanPlain() { + private Token scanPlain() { StringBuilder chunks = new StringBuilder(); Mark startMark = reader.getMark(); Mark endMark = startMark; - Mark spacesStartMark = null; - Mark spacesEndMark = null; int indent = this.indent + 1; String spaces = ""; - CommentToken commentToken = null; while (true) { int c; int length = 0; @@ -2076,24 +2065,14 @@ public final class ScannerImpl implements Scanner { chunks.append(spaces); chunks.append(reader.prefixForward(length)); endMark = reader.getMark(); - spacesStartMark = endMark; spaces = scanPlainSpaces(); - spacesEndMark = reader.getMark(); // System.out.printf("spaces[%s]\n", spaces); if (spaces.length() == 0 || reader.peek() == '#' || (this.flowLevel == 0 && this.reader.getColumn() < indent)) { break; } } - if (reader.peek() == '#') { - commentToken = scanComment(CommentType.IN_LINE); - } - ScalarToken scalarToken = new ScalarToken(chunks.toString(), startMark, endMark, true); - CommentToken blankLineCommentToken = null; - if (spaces.trim().length() > 0) { - blankLineCommentToken = new CommentToken(CommentType.BLANK_LINE, spaces, spacesStartMark, spacesEndMark); - } - return makeTokenList(scalarToken, commentToken, blankLineCommentToken); + return new ScalarToken(chunks.toString(), startMark, endMark, true); } /** @@ -2331,15 +2310,14 @@ public final class ScannerImpl implements Scanner { } return ""; } - - + private List<Token> makeTokenList(Token... tokens) { List<Token> tokenList = new ArrayList<>(); - for(int ix = 0; ix < tokens.length; ix++) { - if(tokens[ix] == null) { + for (int ix = 0; ix < tokens.length; ix++) { + if (tokens[ix] == null) { continue; } - if(!emitComments && (tokens[ix] instanceof CommentToken)) { + if (!emitComments && (tokens[ix] instanceof CommentToken)) { continue; } tokenList.add(tokens[ix]); diff --git a/src/main/java/org/yaml/snakeyaml/serializer/Serializer.java b/src/main/java/org/yaml/snakeyaml/serializer/Serializer.java index fd638b12..ed51e83c 100644 --- a/src/main/java/org/yaml/snakeyaml/serializer/Serializer.java +++ b/src/main/java/org/yaml/snakeyaml/serializer/Serializer.java @@ -24,8 +24,10 @@ import java.util.Set; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions.Version; +import org.yaml.snakeyaml.comments.CommentLine; import org.yaml.snakeyaml.emitter.Emitable; import org.yaml.snakeyaml.events.AliasEvent; +import org.yaml.snakeyaml.events.CommentEvent; import org.yaml.snakeyaml.events.DocumentEndEvent; import org.yaml.snakeyaml.events.DocumentStartEvent; import org.yaml.snakeyaml.events.ImplicitTuple; @@ -37,7 +39,6 @@ import org.yaml.snakeyaml.events.SequenceStartEvent; import org.yaml.snakeyaml.events.StreamEndEvent; import org.yaml.snakeyaml.events.StreamStartEvent; import org.yaml.snakeyaml.nodes.AnchorNode; -import org.yaml.snakeyaml.nodes.CollectionNode; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeId; @@ -151,7 +152,7 @@ public final class Serializer { } } - //parent Node is not used but might be used in the future + // parent Node is not used but might be used in the future private void serializeNode(Node node, Node parent) throws IOException { if (node.getNodeId() == NodeId.anchor) { node = ((AnchorNode) node).getRealNode(); @@ -164,6 +165,7 @@ public final class Serializer { switch (node.getNodeId()) { case scalar: ScalarNode scalarNode = (ScalarNode) node; + serializeComments(node.getBlockComments()); Tag detectedTag = this.resolver.resolve(NodeId.scalar, scalarNode.getValue(), true); Tag defaultTag = this.resolver.resolve(NodeId.scalar, scalarNode.getValue(), false); ImplicitTuple tuple = new ImplicitTuple(node.getTag().equals(detectedTag), node @@ -171,9 +173,12 @@ public final class Serializer { ScalarEvent event = new ScalarEvent(tAlias, node.getTag().getValue(), tuple, scalarNode.getValue(), null, null, scalarNode.getScalarStyle()); this.emitter.emit(event); + serializeComments(node.getInLineComments()); + serializeComments(node.getEndComments()); break; case sequence: SequenceNode seqNode = (SequenceNode) node; + serializeComments(node.getBlockComments()); boolean implicitS = node.getTag().equals(this.resolver.resolve(NodeId.sequence, null, true)); this.emitter.emit(new SequenceStartEvent(tAlias, node.getTag().getValue(), @@ -183,22 +188,40 @@ public final class Serializer { serializeNode(item, node); } this.emitter.emit(new SequenceEndEvent(null, null)); + serializeComments(node.getInLineComments()); + serializeComments(node.getEndComments()); break; default:// instance of MappingNode + serializeComments(node.getBlockComments()); Tag implicitTag = this.resolver.resolve(NodeId.mapping, null, true); boolean implicitM = node.getTag().equals(implicitTag); - this.emitter.emit(new MappingStartEvent(tAlias, node.getTag().getValue(), - implicitM, null, null, ((CollectionNode) node).getFlowStyle())); MappingNode mnode = (MappingNode) node; List<NodeTuple> map = mnode.getValue(); - for (NodeTuple row : map) { - Node key = row.getKeyNode(); - Node value = row.getValueNode(); - serializeNode(key, mnode); - serializeNode(value, mnode); + if (mnode.getTag() != Tag.COMMENT ) { + this.emitter.emit(new MappingStartEvent(tAlias, mnode.getTag().getValue(), implicitM, null, null, + mnode.getFlowStyle())); + for (NodeTuple row : map) { + Node key = row.getKeyNode(); + Node value = row.getValueNode(); + serializeNode(key, mnode); + serializeNode(value, mnode); + } + this.emitter.emit(new MappingEndEvent(null, null)); + serializeComments(node.getInLineComments()); + serializeComments(node.getEndComments()); } - this.emitter.emit(new MappingEndEvent(null, null)); } } } + + private void serializeComments(List<CommentLine> comments) throws IOException { + if(comments == null) { + return; + } + for (CommentLine line : comments) { + CommentEvent commentEvent = new CommentEvent(line.getCommentType(), line.getValue(), line.getStartMark(), + line.getEndMark()); + this.emitter.emit(commentEvent); + } + } } diff --git a/src/main/java/org/yaml/snakeyaml/tokens/CommentToken.java b/src/main/java/org/yaml/snakeyaml/tokens/CommentToken.java index 2e98a730..9dea88fc 100644 --- a/src/main/java/org/yaml/snakeyaml/tokens/CommentToken.java +++ b/src/main/java/org/yaml/snakeyaml/tokens/CommentToken.java @@ -17,15 +17,10 @@ package org.yaml.snakeyaml.tokens; import java.util.Objects; +import org.yaml.snakeyaml.comments.CommentType; import org.yaml.snakeyaml.error.Mark; public final class CommentToken extends Token { - public static enum CommentType { - BLANK_LINE, // - BLOCK, // - IN_LINE; // - } - private final CommentType type; private final String value; diff --git a/src/test/java/org/yaml/snakeyaml/comment/ComposerWithCommentEnabledTest.java b/src/test/java/org/yaml/snakeyaml/comment/ComposerWithCommentEnabledTest.java new file mode 100644 index 00000000..4f592fb3 --- /dev/null +++ b/src/test/java/org/yaml/snakeyaml/comment/ComposerWithCommentEnabledTest.java @@ -0,0 +1,528 @@ +package org.yaml.snakeyaml.comment; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; + +import org.junit.Test; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.comments.CommentLine; +import org.yaml.snakeyaml.composer.Composer; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.events.Event; +import org.yaml.snakeyaml.events.Event.ID; +import org.yaml.snakeyaml.nodes.MappingNode; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.ScalarNode; +import org.yaml.snakeyaml.nodes.SequenceNode; +import org.yaml.snakeyaml.parser.Parser; +import org.yaml.snakeyaml.parser.ParserImpl; +import org.yaml.snakeyaml.reader.StreamReader; +import org.yaml.snakeyaml.resolver.Resolver; + +public class ComposerWithCommentEnabledTest { + + @SuppressWarnings("unused") + private void assertEventListEquals(List<ID> expectedEventIdList, Parser parser) { + for (ID expectedEventId : expectedEventIdList) { + parser.checkEvent(expectedEventId); + Event event = parser.getEvent(); + System.out.println(event); + if (event == null) { + fail("Missing event: " + expectedEventId); + } + assertEquals(expectedEventId, event.getEventId()); + } + } + + private void printBlockComment(Node node, int level, PrintStream out) { + if (node.getBlockComments() != null) { + List<CommentLine> blockComments = node.getBlockComments(); + for (int i = 0; i < blockComments.size(); i++) { + printWithIndent("Block Comment", level, out); + } + } + } + + private void printEndComment(Node node, int level, PrintStream out) { + if (node.getEndComments() != null) { + List<CommentLine> endComments = node.getEndComments(); + for (int i = 0; i < endComments.size(); i++) { + printWithIndent("End Comment", level, out); + } + } + } + + private void printInLineComment(Node node, int level, PrintStream out) { + if (node.getInLineComments() != null) { + List<CommentLine> inLineComments = node.getInLineComments(); + for (int i = 0; i < inLineComments.size(); i++) { + printWithIndent("InLine Comment", level + 1, out); + } + } + } + + private void printWithIndent(String line, int level, PrintStream out) { + for (int ix = 0; ix < level; ix++) { + out.print(" "); + } + out.println(line); + } + + private void printNodeInternal(Node node, int level, PrintStream out) { + + if (node instanceof MappingNode) { + MappingNode mappingNode = (MappingNode) node; + printBlockComment(mappingNode, level, out); + printWithIndent(mappingNode.getClass().getSimpleName(), level, out); + for (NodeTuple childNodeTuple : mappingNode.getValue()) { + printWithIndent("Tuple", level + 1, out); + printNodeInternal(childNodeTuple.getKeyNode(), level + 2, out); + printNodeInternal(childNodeTuple.getValueNode(), level + 2, out); + } + printInLineComment(mappingNode, level, out); + printEndComment(mappingNode, level, out); + + } else if (node instanceof SequenceNode) { + SequenceNode sequenceNode = (SequenceNode) node; + printBlockComment(sequenceNode, level, out); + printWithIndent(sequenceNode.getClass().getSimpleName(), level, out); + for (Node childNode : sequenceNode.getValue()) { + printNodeInternal(childNode, level + 1, out); + } + printInLineComment(sequenceNode, level, out); + printEndComment(sequenceNode, level, out); + + } else if (node instanceof ScalarNode) { + ScalarNode scalarNode = (ScalarNode) node; + printBlockComment(scalarNode, level, out); + printWithIndent(scalarNode.getClass().getSimpleName() + ": " + scalarNode.getValue(), level, out); + printInLineComment(scalarNode, level, out); + printEndComment(scalarNode, level, out); + + } else { + printBlockComment(node, level, out); + printWithIndent(node.getClass().getSimpleName(), level, out); + printInLineComment(node, level, out); + printEndComment(node, level, out); + } + } + + private void printNodeList(List<Node> nodeList) { + System.out.println("BEGIN"); + for (Node node : nodeList) { + printNodeInternal(node, 1, System.out); + } + System.out.println("DONE\n"); + } + + private List<Node> getNodeList(Composer composer) { + List<Node> nodeList = new ArrayList<>(); + while (composer.checkNode()) { + nodeList.add(composer.getNode()); + } + return nodeList; + } + + private void assertNodesEqual(String[] expecteds, List<Node> nodeList) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (PrintStream out = new PrintStream(baos)) { + for (Node node : nodeList) { + printNodeInternal(node, 0, out); + } + } + String actualString = baos.toString(); + String[] actuals = actualString.split("\n"); + for(int ix = 0; ix < Math.min(expecteds.length, actuals.length); ix++) { + assertEquals(expecteds[ix], actuals[ix]); + } + assertEquals(expecteds.length, actuals.length); + } + + public Composer newComposerWithCommentsEnabled(String data) { + return new Composer(new ParserImpl(new StreamReader(data), true), new Resolver()); + } + + @Test + public void testEmpty() { + String data = ""; + String[] expecteds = new String[] { // + "" // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testParseWithOnlyComment() { + String data = "# Comment"; + String[] expecteds = new String[] { // + "Block Comment", // + "MappingNode", // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testCommentEndingALine() { + String data = "" + // + "key: # Comment\n" + // + " value\n"; + + String[] expecteds = new String[] { // + "MappingNode", // + " Tuple", // + " ScalarNode: key", // + " InLine Comment", // + " ScalarNode: value" // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testMultiLineComment() { + String data = "" + // + "key: # Comment\n" + // + " # lines\n" + // + " value\n" + // + "\n"; + + String[] expecteds = new String[] { // + "MappingNode", // + " Tuple", // + " ScalarNode: key", // + " InLine Comment", // + " InLine Comment", // + " ScalarNode: value" // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testBlankLine() { + String data = "" + // + "\n"; + + String[] expecteds = new String[] { // + "Block Comment", // + "MappingNode", // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testBlankLineComments() { + String data = "" + // + "\n" + // + "abc: def # commment\n" + // + "\n" + // + "\n"; + + String[] expecteds = new String[] { // + "Block Comment", // + "MappingNode", // + " Tuple", // + " ScalarNode: abc", // + " ScalarNode: def", // + " InLine Comment", // + "End Comment", // + "End Comment", // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void test_blockScalar() { + String data = "" + // + "abc: > # Comment\n" + // + " def\n" + // + " hij\n" + // + "\n"; + + String[] expecteds = new String[] { // + "MappingNode", // + " Tuple", // + " ScalarNode: abc", // + " InLine Comment", // + " ScalarNode: def hij" // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testDirectiveLineEndComment() { + String data = "%YAML 1.1 #Comment\n"; + + String[] expecteds = new String[] { // + "" // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testSequence() { + String data = "" + // + "# Comment\n" + // + "list: # InlineComment1\n" + // + "# Block Comment\n" + // + "- item # InlineComment2\n" + // + "# Comment\n"; + + String[] expecteds = new String[] { // + "Block Comment", // + "MappingNode", // + " Tuple", // + " ScalarNode: list", // + " InLine Comment", // + " Block Comment", // + " SequenceNode", // + " ScalarNode: item", // + " InLine Comment", // + "End Comment" // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testAllComments1() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "# Block Comment2\n" + // + "key: # Inline Comment1a\n" + // + " # Inline Comment1b\n" + // + " # Block Comment3a\n" + // + " # Block Comment3b\n" + // + " value # Inline Comment2\n" + // + "# Block Comment4\n" + // + "list: # InlineComment3a\n" + // + " # InlineComment3b\n" + // + "# Block Comment5\n" + // + "- item1 # InlineComment4\n" + // + "- item2: [ value2a, value2b ] # InlineComment5\n" + // + "- item3: { key3a: [ value3a1, value3a2 ], key3b: value3b } # InlineComment6\n" + // + "# Block Comment6\n" + // + "---\n" + // + "# Block Comment7\n" + // + ""; + + String[] expecteds = new String[] { // + "Block Comment", // + "Block Comment", // + "MappingNode", // + " Tuple", // + " ScalarNode: key", // + " InLine Comment", // + " InLine Comment", // + " Block Comment", // + " Block Comment", // + " ScalarNode: value", // + " InLine Comment", // + " Tuple", // + " Block Comment", // + " ScalarNode: list", // + " InLine Comment", // + " InLine Comment", // + " Block Comment", // + " SequenceNode", // + " ScalarNode: item1", // + " InLine Comment", // + " MappingNode", // + " Tuple", // + " ScalarNode: item2", // + " SequenceNode", // + " ScalarNode: value2a", // + " ScalarNode: value2b", // + " InLine Comment", // + " MappingNode", // + " Tuple", // + " ScalarNode: item3", // + " MappingNode", // + " Tuple", // + " ScalarNode: key3a", // + " SequenceNode", // + " ScalarNode: value3a1", // + " ScalarNode: value3a2", // + " Tuple", // + " ScalarNode: key3b", // + " ScalarNode: value3b", // + " InLine Comment", // + "End Comment", // + "Block Comment", // + "ScalarNode: ", // FIXME: should not be here + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testAllComments2() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "# Block Comment2\n" + // + "- item1 # Inline Comment1a\n" + // + " # Inline Comment1b\n" + // + "# Block Comment3a\n" + // + "# Block Comment3b\n" + // + "- item2: value # Inline Comment2\n" + // + "# Block Comment4\n" + // + ""; + + String[] expecteds = new String[] { // + "Block Comment", // + "Block Comment", // + "SequenceNode", // + " ScalarNode: item1", // + " InLine Comment", // + " InLine Comment", // + " Block Comment", // + " Block Comment", // + " MappingNode", // + " Tuple", // + " ScalarNode: item2", // + " ScalarNode: value", // + " InLine Comment", // + "End Comment", // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + Node newNode = new Yaml().compose(new StringReader("a: b")); + result.add(newNode); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testAllComments3() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "[ item1, item2: value2, {item3: value3} ] # Inline Comment1\n" + // + "# Block Comment2\n" + // + ""; + + String[] expecteds = new String[] { // + "Block Comment", // + "SequenceNode", // + " ScalarNode: item1", // + " MappingNode", // + " Tuple", // + " ScalarNode: item2", // + " ScalarNode: value2", // + " MappingNode", // + " Tuple", // + " ScalarNode: item3", // + " ScalarNode: value3", // + " InLine Comment", // + "End Comment", // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = getNodeList(sut); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + @Test + public void testGetSingleNode() { + String data = "" + // + "\n" + // + "abc: def # commment\n" + // + "\n" + // + "\n"; + String[] expecteds = new String[] { // + "MappingNode", // + " Tuple", // + " ScalarNode: abc", // + " ScalarNode: def", // + " InLine Comment", // + "End Comment", // + "End Comment", // + }; + + Composer sut = newComposerWithCommentsEnabled(data); + List<Node> result = Arrays.asList(new Node[] { sut.getSingleNode() }); + + printNodeList(result); + assertNodesEqual(expecteds, result); + } + + private static class TestConstructor extends SafeConstructor { + } + + @Test + public void testBaseConstructorGetData() { + String data = "" + // + "\n" + // + "abc: def # commment\n" + // + "\n" + // + "\n"; + + TestConstructor sut = new TestConstructor(); + sut.setComposer(newComposerWithCommentsEnabled(data)); + Object result = sut.getData(); + assertTrue(result instanceof LinkedHashMap); + @SuppressWarnings("unchecked") + LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) result; + assertEquals(1, map.size()); + assertEquals(map.get("abc"), "def"); + } +} diff --git a/src/test/java/org/yaml/snakeyaml/comment/EmitterWithCommentEnabledTest.java b/src/test/java/org/yaml/snakeyaml/comment/EmitterWithCommentEnabledTest.java new file mode 100644 index 00000000..4bb8a174 --- /dev/null +++ b/src/test/java/org/yaml/snakeyaml/comment/EmitterWithCommentEnabledTest.java @@ -0,0 +1,213 @@ +package org.yaml.snakeyaml.comment; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.StringWriter; + +import org.junit.Test; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.DumperOptions.ScalarStyle; +import org.yaml.snakeyaml.composer.Composer; +import org.yaml.snakeyaml.emitter.Emitter; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.parser.ParserImpl; +import org.yaml.snakeyaml.reader.StreamReader; +import org.yaml.snakeyaml.resolver.Resolver; +import org.yaml.snakeyaml.serializer.Serializer; + +public class EmitterWithCommentEnabledTest { + + private String runEmitterWithCommentsEnabled(String data) throws IOException { + StringWriter output = new StringWriter(); + + Tag rootTag = null; + DumperOptions options = new DumperOptions(); + options.setDefaultScalarStyle(ScalarStyle.PLAIN); + options.setDefaultFlowStyle(FlowStyle.BLOCK); + Serializer serializer = new Serializer(new Emitter(output, options), new Resolver(), options, rootTag); + + serializer.open(); + Composer composer = new Composer(new ParserImpl(new StreamReader(data), true), new Resolver()); + while (composer.checkNode()) { + serializer.serialize(composer.getNode()); + } + serializer.close(); + + return output.toString(); + } + + @Test + public void testEmpty() throws Exception { + String data = ""; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testWithOnlyComment() throws Exception { + String data = "# Comment\n\n"; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testCommentEndingALine() throws Exception { + String data = "" + // + "key: # Comment\n" + // + " value\n"; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testMultiLineComment() throws Exception { + String data = "" + // + "key: # Comment\n" + // + " # lines\n" + // + " value\n"; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testBlankLine() throws Exception { + String data = "" + // + "\n"; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testBlankLineComments() throws Exception { + String data = "" + // + "\n" + // + "abc: def # commment\n" + // + "\n" + // + "\n"; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testBlockScalar() throws Exception { + String data = "" + // + "abc: | # Comment\n" + // + " def\n" + // + " hij\n"; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testDirectiveLineEndComment() throws Exception { + String data = "%YAML 1.1 #Comment\n"; + + String result = runEmitterWithCommentsEnabled(data); + + // We currently strip Directive comments + assertEquals("", result); + } + + @Test + public void testSequence() throws Exception { + String data = "" + // + "# Comment\n" + // + "list: # InlineComment1\n" + // + " # Block Comment\n" + // + " - item # InlineComment2\n" + // + "# Comment\n"; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testAllComments1() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "# Block Comment2\n" + // + "key: # Inline Comment1a\n" + // + " # Inline Comment1b\n" + // + " # Block Comment3a\n" + // + " # Block Comment3b\n" + // + " value # Inline Comment2\n" + // + "# Block Comment4\n" + // + "list: # InlineComment3a\n" + // + " # InlineComment3b\n" + // + " # Block Comment5\n" + // + " - item1 # InlineComment4\n" + // + " - item2: [value2a, value2b] # InlineComment5\n" + // + " - item3: {key3a: [value3a1, value3a2], key3b: value3b} # InlineComment6\n" + // + "# Block Comment6\n" + // + "---\n" + // + "# Block Comment7\n" + // + ""; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testMultiDoc() throws Exception { + String data = "" + // + "key: value\n" + // + "# Block Comment\n" + // + "---\n" + // + "# Block Comment\n" + // + "key: value\n" + // + ""; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testAllComments2() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "# Block Comment2\n" + // + "- item1 # Inline Comment1a\n" + // + " # Inline Comment1b\n" + // + "# Block Comment3a\n" + // + "# Block Comment3b\n" + // + "- item2: value # Inline Comment2\n" + // + "# Block Comment4\n" + // + ""; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } + + @Test + public void testAllComments3() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "[item1, {item2: value2}, {item3: value3}] # Inline Comment1\n" + // + "# Block Comment2\n" + // + ""; + + String result = runEmitterWithCommentsEnabled(data); + + assertEquals(data, result); + } +} diff --git a/src/test/java/org/yaml/snakeyaml/comment/ParserWithCommentEnabledTest.java b/src/test/java/org/yaml/snakeyaml/comment/ParserWithCommentEnabledTest.java index 7613e2de..5a31e082 100644 --- a/src/test/java/org/yaml/snakeyaml/comment/ParserWithCommentEnabledTest.java +++ b/src/test/java/org/yaml/snakeyaml/comment/ParserWithCommentEnabledTest.java @@ -19,7 +19,9 @@ public class ParserWithCommentEnabledTest { for (ID expectedEventId : expectedEventIdList) { parser.checkEvent(expectedEventId); Event event = parser.getEvent(); - System.out.println(event); + System.out.println("Expected: " + expectedEventId); + System.out.println("Got: " + event); + System.out.println(); if (event == null) { fail("Missing event: " + expectedEventId); } @@ -30,7 +32,8 @@ public class ParserWithCommentEnabledTest { @SuppressWarnings("unused") private void printEventList(Parser parser) { for (Event event = parser.getEvent(); event != null; event = parser.getEvent()) { - System.out.println(event); + System.out.println("Got: " + event); + System.out.println(); } } @@ -174,4 +177,177 @@ public class ParserWithCommentEnabledTest { Parser sut = new ParserImpl(new StreamReader(data), true); assertEventListEquals(expectedEventIdList, sut); } + + @Test + public void testSequence() { + String data = "" + // + "# Comment\n" + // + "list: # InlineComment1\n" + // + "# Block Comment\n" + // + "- item # InlineComment2\n" + // + "# Comment\n"; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.Comment, // + ID.DocumentStart, // + ID.MappingStart, // + ID.Scalar, ID.Comment, + ID.Comment, // + ID.SequenceStart, // + ID.Scalar, ID.Comment, // + ID.Comment, // + ID.SequenceEnd, // + ID.MappingEnd, // + ID.DocumentEnd, // + ID.StreamEnd // + }); + + Parser sut = new ParserImpl(new StreamReader(data), true); + + assertEventListEquals(expectedEventIdList, sut); + } + + @Test + public void testAllComments1() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "# Block Comment2\n" + // + "key: # Inline Comment1a\n" + // + " # Inline Comment1b\n" + // + " # Block Comment3a\n" + // + " # Block Comment3b\n" + // + " value # Inline Comment2\n" + // + "# Block Comment4\n" + // + "list: # InlineComment3a\n" + // + " # InlineComment3b\n" + // + "# Block Comment5\n" + // + "- item1 # InlineComment4\n" + // + "- item2: [ value2a, value2b ] # InlineComment5\n" + // + "- item3: { key3a: [ value3a1, value3a2 ], key3b: value3b } # InlineComment6\n" + // + "# Block Comment6\n" + // + "---\n" + // + "# Block Comment7\n" + // + ""; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.Comment, // + ID.Comment, // + ID.DocumentStart, // + ID.MappingStart, // + ID.Scalar, ID.Comment, ID.Comment, // + + ID.Comment, ID.Comment, // + ID.Scalar, ID.Comment, // + + ID.Comment, // + ID.Scalar, ID.Comment, ID.Comment, // + ID.Comment, // + + ID.SequenceStart, // + ID.Scalar, ID.Comment, // + ID.MappingStart, // + ID.Scalar, ID.SequenceStart, ID.Scalar, ID.Scalar, ID.SequenceEnd, ID.Comment, // + ID.MappingEnd, + + ID.MappingStart, // + ID.Scalar, // value=item3 + ID.MappingStart, // + ID.Scalar, // value=key3a + ID.SequenceStart, // + ID.Scalar, // value=value3a + ID.Scalar, //value=value3a2 + ID.SequenceEnd, // + ID.Scalar, // value=key3b + ID.Scalar, // value=value3b + ID.MappingEnd, // + ID.Comment, // type=IN_LINE, value= InlineComment6 + ID.Comment, // + ID.MappingEnd, // + ID.SequenceEnd, // + ID.MappingEnd, + ID.DocumentEnd, // + + ID.DocumentStart, // + ID.Comment, // + ID.Scalar, // Empty + ID.DocumentEnd, // + ID.StreamEnd // + }); + + Parser sut = new ParserImpl(new StreamReader(data), true); + + //printEventList(sut); + assertEventListEquals(expectedEventIdList, sut); + } + + @Test + public void testAllComments2() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "# Block Comment2\n" + // + "- item1 # Inline Comment1a\n" + // + " # Inline Comment1b\n" + // + "# Block Comment3a\n" + // + "# Block Comment3b\n" + // + "- item2: value # Inline Comment2\n" + // + "# Block Comment4\n" + // + ""; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.Comment, // + ID.Comment, // + ID.DocumentStart, // + ID.SequenceStart, // + ID.Scalar, ID.Comment, ID.Comment, // + ID.Comment, // + ID.Comment, // + ID.MappingStart, // + ID.Scalar, ID.Scalar, ID.Comment, // + ID.Comment, // + ID.MappingEnd, // + ID.SequenceEnd, // + ID.DocumentEnd, // + ID.StreamEnd // + }); + + Parser sut = new ParserImpl(new StreamReader(data), true); + + assertEventListEquals(expectedEventIdList, sut); + } + + @Test + public void testAllComments3() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "[ item1, item2: value2, {item3: value3} ] # Inline Comment1\n" + // + "# Block Comment2\n" + // + ""; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.Comment, // + ID.DocumentStart, // + ID.SequenceStart, // + ID.Scalar, + ID.MappingStart, // + ID.Scalar, ID.Scalar, // + ID.MappingEnd, // + ID.MappingStart, // + ID.Scalar, ID.Scalar, // + ID.MappingEnd, // + ID.SequenceEnd, // + ID.Comment, // + ID.Comment, // + ID.DocumentEnd, // + ID.StreamEnd // + }); + + Parser sut = new ParserImpl(new StreamReader(data), true); + +// printEventList(sut); + assertEventListEquals(expectedEventIdList, sut); + } } diff --git a/src/test/java/org/yaml/snakeyaml/comment/SerializerWithCommentEnabledTest.java b/src/test/java/org/yaml/snakeyaml/comment/SerializerWithCommentEnabledTest.java new file mode 100644 index 00000000..1ed7ccb9 --- /dev/null +++ b/src/test/java/org/yaml/snakeyaml/comment/SerializerWithCommentEnabledTest.java @@ -0,0 +1,389 @@ +package org.yaml.snakeyaml.comment; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.composer.Composer; +import org.yaml.snakeyaml.emitter.Emitable; +import org.yaml.snakeyaml.events.Event; +import org.yaml.snakeyaml.events.Event.ID; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.parser.ParserImpl; +import org.yaml.snakeyaml.reader.StreamReader; +import org.yaml.snakeyaml.resolver.Resolver; +import org.yaml.snakeyaml.serializer.Serializer; + +public class SerializerWithCommentEnabledTest { + + private void assertEventListEquals(List<ID> expectedEventIdList, List<Event> actualEvents) { + Iterator<Event> iterator = actualEvents.iterator(); + for (ID expectedEventId : expectedEventIdList) { + System.out.println("Expected: " + expectedEventId); + assertTrue(iterator.hasNext()); + Event event = iterator.next(); + System.out.println("Got: " + event); + System.out.println(); + assertEquals(expectedEventId, event.getEventId()); + } + } + + private static class TestEmitter implements Emitable { + private List<Event> eventList = new ArrayList<>(); + + @Override + public void emit(Event event) throws IOException { + eventList.add(event); + } + + public List<Event> getEventList() { + return eventList; + } + } + + public List<Event> serializeWithCommentsEnabled(String data) throws IOException { + TestEmitter emitter = new TestEmitter(); + Tag rootTag = null; + Serializer serializer = new Serializer(emitter, new Resolver(), new DumperOptions(), rootTag); + serializer.open(); + Composer composer = new Composer(new ParserImpl(new StreamReader(data), true), new Resolver()); + while (composer.checkNode()) { + serializer.serialize(composer.getNode()); + } + serializer.close(); + List<Event> events = emitter.getEventList(); + System.out.println("RESULT: "); + for(Event event: events) { + System.out.println(event); + } + System.out.println(); + return events; + } + + + @Test + public void testEmpty() throws Exception { + List<ID> expectedEventIdList = Arrays.asList(new ID[] { ID.StreamStart, ID.StreamEnd }); + + String data = ""; + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testParseWithOnlyComment() throws Exception { + String data = "# Comment"; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.DocumentStart, // + ID.Comment, // + ID.DocumentEnd, // + ID.StreamEnd, // + }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testCommentEndingALine() throws Exception { + String data = "" + // + "key: # Comment\n" + // + " value\n"; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.DocumentStart, // + ID.MappingStart, // + ID.Scalar, ID.Comment, ID.Scalar, // + ID.MappingEnd, // + ID.DocumentEnd, // + ID.StreamEnd }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testMultiLineComment() throws Exception { + String data = "" + // + "key: # Comment\n" + // + " # lines\n" + // + " value\n" + // + "\n"; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { ID.StreamStart, // + ID.DocumentStart, // + ID.MappingStart, // + ID.Scalar, ID.Comment, ID.Comment, ID.Scalar, // + ID.MappingEnd, // + ID.DocumentEnd, // + ID.StreamEnd }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testBlankLine() throws Exception { + String data = "" + // + "\n"; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.DocumentStart, // + ID.Comment, // + ID.DocumentEnd, // + ID.StreamEnd }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testBlankLineComments() throws Exception { + String data = "" + // + "\n" + // + "abc: def # commment\n" + // + "\n" + // + "\n"; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.DocumentStart, // + ID.Comment, // + ID.MappingStart, // + ID.Scalar, ID.Scalar, ID.Comment, // + ID.MappingEnd, // + ID.Comment, // + ID.Comment, // + ID.DocumentEnd, // + ID.StreamEnd }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void test_blockScalar() throws Exception { + String data = "" + // + "abc: > # Comment\n" + // + " def\n" + // + " hij\n" + // + "\n"; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.DocumentStart, // + ID.MappingStart, // + ID.Scalar, ID.Comment, // + ID.Scalar, // + ID.MappingEnd, // + ID.DocumentEnd, // + ID.StreamEnd // + }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testDirectiveLineEndComment() throws Exception { + String data = "%YAML 1.1 #Comment\n"; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.StreamEnd // + }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testSequence() throws Exception { + String data = "" + // + "# Comment\n" + // + "list: # InlineComment1\n" + // + "# Block Comment\n" + // + "- item # InlineComment2\n" + // + "# Comment\n"; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.DocumentStart, // + ID.Comment, // + ID.MappingStart, // + ID.Scalar, ID.Comment, ID.Comment, // + ID.SequenceStart, // + ID.Scalar, ID.Comment, // + ID.SequenceEnd, // + ID.MappingEnd, // + ID.Comment, // + ID.DocumentEnd, // + ID.StreamEnd // + }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testAllComments1() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "# Block Comment2\n" + // + "key: # Inline Comment1a\n" + // + " # Inline Comment1b\n" + // + " # Block Comment3a\n" + // + " # Block Comment3b\n" + // + " value # Inline Comment2\n" + // + "# Block Comment4\n" + // + "list: # InlineComment3a\n" + // + " # InlineComment3b\n" + // + "# Block Comment5\n" + // + "- item1 # InlineComment4\n" + // + "- item2: [ value2a, value2b ] # InlineComment5\n" + // + "- item3: { key3a: [ value3a1, value3a2 ], key3b: value3b } # InlineComment6\n" + // + "# Block Comment6\n" + // + "---\n" + // + "# Block Comment7\n" + // + ""; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.DocumentStart, // + ID.Comment, // + ID.Comment, // + ID.MappingStart, // + ID.Scalar, ID.Comment, ID.Comment, // + + ID.Comment, ID.Comment, // + ID.Scalar, ID.Comment, // + + ID.Comment, // + ID.Scalar, ID.Comment, ID.Comment, // + ID.Comment, // + + ID.SequenceStart, // + ID.Scalar, ID.Comment, // + ID.MappingStart, // + ID.Scalar, ID.SequenceStart, ID.Scalar, ID.Scalar, ID.SequenceEnd, ID.Comment, // + ID.MappingEnd, + + ID.MappingStart, // + ID.Scalar, // value=item3 + ID.MappingStart, // + ID.Scalar, // value=key3a + ID.SequenceStart, // + ID.Scalar, // value=value3a + ID.Scalar, // value=value3a2 + ID.SequenceEnd, // + ID.Scalar, // value=key3b + ID.Scalar, // value=value3b + ID.MappingEnd, // + ID.Comment, // type=IN_LINE, value= InlineComment6 + ID.MappingEnd, // + ID.SequenceEnd, // + ID.MappingEnd, // + ID.Comment, // + ID.DocumentEnd, // + + ID.DocumentStart, // + ID.Comment, // + ID.Scalar, // Empty + ID.DocumentEnd, // + ID.StreamEnd // + }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testAllComments2() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "# Block Comment2\n" + // + "- item1 # Inline Comment1a\n" + // + " # Inline Comment1b\n" + // + "# Block Comment3a\n" + // + "# Block Comment3b\n" + // + "- item2: value # Inline Comment2\n" + // + "# Block Comment4\n" + // + ""; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.DocumentStart, // + ID.Comment, // + ID.Comment, // + ID.SequenceStart, // + ID.Scalar, ID.Comment, ID.Comment, // + ID.Comment, // + ID.Comment, // + ID.MappingStart, // + ID.Scalar, ID.Scalar, ID.Comment, // + ID.MappingEnd, // + ID.SequenceEnd, // + ID.Comment, // + ID.DocumentEnd, // + ID.StreamEnd // + }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } + + @Test + public void testAllComments3() throws Exception { + String data = "" + // + "# Block Comment1\n" + // + "[ item1, item2: value2, {item3: value3} ] # Inline Comment1\n" + // + "# Block Comment2\n" + // + ""; + + List<ID> expectedEventIdList = Arrays.asList(new ID[] { // + ID.StreamStart, // + ID.DocumentStart, // + ID.Comment, // + ID.SequenceStart, // + ID.Scalar, ID.MappingStart, // + ID.Scalar, ID.Scalar, // + ID.MappingEnd, // + ID.MappingStart, // + ID.Scalar, ID.Scalar, // + ID.MappingEnd, // + ID.SequenceEnd, // + ID.Comment, // + ID.Comment, // + ID.DocumentEnd, // + ID.StreamEnd // + }); + + List<Event> result = serializeWithCommentsEnabled(data); + + assertEventListEquals(expectedEventIdList, result); + } +} diff --git a/src/test/java/org/yaml/snakeyaml/emitter/EmitterTest.java b/src/test/java/org/yaml/snakeyaml/emitter/EmitterTest.java index 11a13598..c810137e 100644 --- a/src/test/java/org/yaml/snakeyaml/emitter/EmitterTest.java +++ b/src/test/java/org/yaml/snakeyaml/emitter/EmitterTest.java @@ -130,6 +130,10 @@ public class EmitterTest extends TestCase { emitter.emit(new DocumentStartEvent(null, null, false, null, null)); emitter.emit(new ScalarEvent(null, null, new ImplicitTuple(true, false), burger + halfBurger, null, null, DumperOptions.ScalarStyle.DOUBLE_QUOTED)); + // Needed as emitter won't process above event until it peeks at this one + // to be sure it is not a comment + emitter.emit(new ScalarEvent(null, null, new ImplicitTuple(true, false), "", null, + null, DumperOptions.ScalarStyle.PLAIN)); String expected = "! \"\\U0001f354\\ud83c\""; assertEquals(expected, output.toString()); } diff --git a/src/test/java/org/yaml/snakeyaml/emitter/EmptyStringOutputTest.java b/src/test/java/org/yaml/snakeyaml/emitter/EmptyStringOutputTest.java index 3df77520..4540e199 100644 --- a/src/test/java/org/yaml/snakeyaml/emitter/EmptyStringOutputTest.java +++ b/src/test/java/org/yaml/snakeyaml/emitter/EmptyStringOutputTest.java @@ -45,6 +45,9 @@ public class EmptyStringOutputTest extends TestCase { emitter.emit(new StreamStartEvent(null, null)); emitter.emit(new DocumentStartEvent(null, null, false, null, null)); emitter.emit(new ScalarEvent(null, null, new ImplicitTuple(true, false), value, null, null, DumperOptions.ScalarStyle.PLAIN)); + // Needed as emitter won't process above event until it peeks at this one + // to be sure it is not a comment + emitter.emit(new ScalarEvent(null, null, new ImplicitTuple(true, false), value, null, null, DumperOptions.ScalarStyle.PLAIN)); return output.toString(); } } |