Repository: decorators-squad/eo-yaml
Pull request #614 (2024-03-31 15:29:43)

#607 Read flow nodes from ReadYamlMapping
PR for #607 

We are now able to read flow-style nodes contained within a read bolck-style YamlMapping.


Patch:
diff --git a/src/main/java/com/amihaiemil/eoyaml/CollapsedFlowLines.java b/src/main/java/com/amihaiemil/eoyaml/CollapsedFlowLines.java
new file mode 100644
index 0000000..35988e4
--- /dev/null
+++ b/src/main/java/com/amihaiemil/eoyaml/CollapsedFlowLines.java
@@ -0,0 +1,168 @@
+/**
+ * Copyright (c) 2016-2024, Mihai Emil Andronache
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+package com.amihaiemil.eoyaml;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Returns an iterator which collapses the flow-style lines into one single
+ * line.<br>
+ * <pre>
+ * //collapse the lines of flow-style sequeces
+ * final YamlLines collapsed = new CollapsedFlowLines(
+ *     new AllYamlLines(lines), '[', ']'
+ * );
+ * </pre>
+ * Initial lines:<br>
+ * <pre>
+ * ---
+ * name: eo-yaml
+ * developers: [
+ *   amihaiemil,
+ *   sherif,
+ *   rultor]
+ * architect: amihaiemil
+ * </pre>
+ * Lines as iterated by this class:<br>
+ * <pre>
+ * ---
+ * name: eo-yaml
+ * developers: [amihaiemil, sherif, rultor]
+ * architect: amihaiemil
+ * </pre>
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @checkstyle ExecutableStatementCount (1000 lines)
+ * @checkstyle CyclomaticComplexity (1000 lines)
+ * @since 8.0.0
+ */
+final class CollapsedFlowLines implements YamlLines {
+    /**
+     * The flow's opening bracket.
+     */
+    private final char openingBracket;
+
+    /**
+     * The flow's closing bracket.
+     */
+    private final char closingBracket;
+
+    /**
+     * Lines to fold.
+     */
+    private final YamlLines lines;
+
+    /**
+     * Ctor.
+     * @param lines Lines to fold.
+     * @param openingBracket Opening.
+     * @param closingBracket Closing.
+     */
+    CollapsedFlowLines(
+        final YamlLines lines,
+        final char openingBracket,
+        final char closingBracket
+    ) {
+        this.lines = lines;
+        this.openingBracket = openingBracket;
+        this.closingBracket = closingBracket;
+    }
+
+    @Override
+    public Collection<YamlLine> original() {
+        return this.lines.original();
+    }
+
+    @Override
+    public YamlNode nextYamlNode(final YamlLine prev) {
+        return this.lines.nextYamlNode(prev);
+    }
+
+    @Override
+    public Iterator<YamlLine> iterator() {
+        int bracketCount = 0;
+        boolean closedBracketFound = false;
+        final List<YamlLine> collapsed = new ArrayList<>();
+        List<YamlLine> flow = new ArrayList<>();
+        boolean startedQuoteEscape = false;
+        boolean startedApEscape = false;
+        for(final YamlLine line : this.lines) {
+            String text = line.trimmed();
+            for (int i = 0; i < text.length(); i++) {
+                final char currentChar = text.charAt(i);
+                if(currentChar == this.openingBracket
+                    && !startedApEscape && !startedQuoteEscape) {
+                    bracketCount++;
+                } else if(currentChar == this.closingBracket
+                    && !startedApEscape && !startedQuoteEscape) {
+                    bracketCount--;
+                    closedBracketFound = true;
+                }
+                if (isEscapeChar(i, '\"', text) && !startedApEscape) {
+                    startedQuoteEscape = !startedQuoteEscape;
+                } else if (isEscapeChar(i, '\'', text) && !startedQuoteEscape) {
+                    startedApEscape = !startedApEscape;
+                }
+            }
+            if(bracketCount % 2 != 0) {
+                flow.add(line);
+            } else if(bracketCount == 0 && closedBracketFound) {
+                flow.add(line);
+                collapsed.add(new CollapsedYamlLine(flow));
+                flow = new ArrayList<>();
+                closedBracketFound = false;
+            } else {
+                collapsed.add(line);
+            }
+        }
+        return collapsed.iterator();
+    }
+
+    /**
+     * Does the string contain an escape chartacter at the specified index?
+     * @param start Index.
+     * @param escapeChar Escape character to look for.
+     * @param nodes String containing the characters.
+     * @return True if the escape character is found and it is
+     *  not preceded by a backslash.
+     */
+    private boolean isEscapeChar(
+        final int start, final char escapeChar, final String nodes
+    ) {
+        final boolean result;
+        if (nodes.charAt(start) == escapeChar) {
+            result = start == 0 || nodes.charAt(start - 1) != '\\';
+        } else {
+            result = false;
+        }
+        return result;
+    }
+}
diff --git a/src/main/java/com/amihaiemil/eoyaml/CollapsedYamlLine.java b/src/main/java/com/amihaiemil/eoyaml/CollapsedYamlLine.java
new file mode 100644
index 0000000..d5204f4
--- /dev/null
+++ b/src/main/java/com/amihaiemil/eoyaml/CollapsedYamlLine.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2016-2024, Mihai Emil Andronache
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+package com.amihaiemil.eoyaml;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * YamlLine made up of multiple YAML lines collapsed onto one.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @since 8.0.0
+ */
+final class CollapsedYamlLine implements YamlLine {
+
+    /**
+     * Yaml lines to collapse onto one single line.
+     */
+    private final List<YamlLine> lines;
+
+    /**
+     * Ctor.
+     * @param lines Yaml lines to collapse onto one single line.
+     */
+    CollapsedYamlLine(final List<YamlLine> lines) {
+        this.lines = lines;
+    }
+
+    @Override
+    public String value() {
+        return this.lines.stream()
+            .map(YamlLine::value)
+            .collect(Collectors.joining(" "));
+    }
+
+    @Override
+    public int number() {
+        final int number;
+        if(this.lines.isEmpty()) {
+            number = -1;
+        } else {
+            number = this.lines.get(0).number();
+        }
+        return number;
+    }
+
+    @Override
+    public int indentation() {
+        final int indentation;
+        if(this.lines.isEmpty()) {
+            indentation = -1;
+        } else {
+            indentation = this.lines.get(0).indentation();
+        }
+        return indentation;
+    }
+
+    @Override
+    public String toString() {
+        return this.value();
+    }
+}
diff --git a/src/main/java/com/amihaiemil/eoyaml/FoldedFlowLines.java b/src/main/java/com/amihaiemil/eoyaml/FoldedFlowLines.java
index 9e7dc1d..4324a5b 100644
--- a/src/main/java/com/amihaiemil/eoyaml/FoldedFlowLines.java
+++ b/src/main/java/com/amihaiemil/eoyaml/FoldedFlowLines.java
@@ -37,8 +37,9 @@ import java.util.List;
  * The iterator will fold all the lines into a single line.
  * @author Mihai Andronache (amihaiemil@gmail.com)
  * @version $Id$
- * @since 6.0.0
+ * @since 8.0.0
  */
+@Deprecated
 final class FoldedFlowLines implements YamlLines {
 
     /**
@@ -62,6 +63,7 @@ final class FoldedFlowLines implements YamlLines {
      * @param openingBracket Opening.
      * @param closingBracket Closing.
      */
+    @Deprecated
     FoldedFlowLines(
         final YamlLines lines,
         final char openingBracket,
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadFlowMapping.java b/src/main/java/com/amihaiemil/eoyaml/ReadFlowMapping.java
index e6c892b..cdff985 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadFlowMapping.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadFlowMapping.java
@@ -40,9 +40,13 @@ import java.util.Set;
  * between curly brackets, separated by a comma.
  * @author Mihai Andronache (amihaiemil@gmail.com)
  * @version $Id$
- * @since 6.0.0
+ * @since 8.0.0
  * @todo #601:30min Modify the yaml printing visitor, so it prints the flow
  *  nodes in Flow style instead of block.
+ * @todo #607:60min Refactor this class to not use FoldedFlowLines anymore.
+ *  FoldedFlowLines is deprecated and the collapsing/folding logic is now
+ *  implemented with {@link CollapsedFlowLines}. ReadFlowSequence needs the
+ *  same refactoring after that.
  */
 final class ReadFlowMapping extends BaseYamlMapping {
 
@@ -150,7 +154,7 @@ final class ReadFlowMapping extends BaseYamlMapping {
      * This mappings's entries in String format.
      * @author Mihai Andronache (amihaiemil@gmail.com)
      * @version $Id$
-     * @since 7.3.0
+     * @since 8.0.0
      * @checkstyle LineLength (100 lines)
      * @checkstyle ModifiedControlVariable (100 lines)
      * @checkstyle ParameterNumber (300 lines)
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadFlowSequence.java b/src/main/java/com/amihaiemil/eoyaml/ReadFlowSequence.java
index 0f13926..4fa7586 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadFlowSequence.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadFlowSequence.java
@@ -36,7 +36,7 @@ import java.util.List;
  * A read YamlSequence in flow format (elements between square brackets).
  * @author Mihai Andronache (amihaiemil@gmail.com)
  * @version $Id$
- * @since 6.0.0
+ * @since 8.0.0
  */
 final class ReadFlowSequence extends BaseYamlSequence {
 
@@ -125,7 +125,7 @@ final class ReadFlowSequence extends BaseYamlSequence {
      * This sequence's nodes in String format.
      * @author Mihai Andronache (amihaiemil@gmail.com)
      * @version $Id$
-     * @since 6.0.0
+     * @since 8.0.0
      * @checkstyle LineLength (100 lines)
      * @checkstyle ModifiedControlVariable (100 lines)
      * @checkstyle ParameterNumber (300 lines)
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java b/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
index 9d07142..a3b939e 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadYamlMapping.java
@@ -38,7 +38,7 @@ import java.util.regex.Pattern;
 import static com.amihaiemil.eoyaml.YamlLine.UNKNOWN_LINE_NUMBER;
 
 /**
- * YamlMapping read from somewhere. YAML directives and
+ * Block-style YamlMapping read from somewhere. YAML directives and
  * document start/end markers are ignored. This is assumed
  * to be a plain YAML mapping.
  * @checkstyle CyclomaticComplexity (300 lines)
@@ -46,8 +46,6 @@ import static com.amihaiemil.eoyaml.YamlLine.UNKNOWN_LINE_NUMBER;
  * @author Mihai Andronache (amihaiemil@gmail.com)
  * @version $Id$
  * @since 1.0.0
- * @todo #600:60min Make sure to also read flow-style YamlNodes as such from
- *  a read block YamlMapping.
  */
 final class ReadYamlMapping extends BaseYamlMapping {
 
@@ -55,7 +53,8 @@ final class ReadYamlMapping extends BaseYamlMapping {
      * Regex for a key in a mapping.
      */
     private static final Pattern KEY_PATTERN = Pattern.compile(
-        "^-?\\s*((?<key>[^:'\"]+)|\"(?<keyQ>.+)\"|'(?<keySQ>.+)'):(|\\s.*)$"
+        "^-?\\s*((?<key>[^:',\"}{\\]\\[]+)|\"(?<keyQ>.+)\"|'(?<keySQ>.+)')"
+            + ":(|\\s.*)$"
     );
 
     /**
@@ -115,14 +114,18 @@ final class ReadYamlMapping extends BaseYamlMapping {
         this.all = lines;
         this.significant = new SameIndentationLevel(
             new WellIndented(
-                new Skip(
-                    lines,
-                    line -> line.number() <= previous.number(),
-                    line -> line.trimmed().startsWith("#"),
-                    line -> line.trimmed().startsWith("---"),
-                    line -> line.trimmed().startsWith("..."),
-                    line -> line.trimmed().startsWith("%"),
-                    line -> line.trimmed().startsWith("!!")
+                new CollapsedFlowLines(
+                    new CollapsedFlowLines(
+                        new Skip(
+                            lines,
+                            line -> line.number() <= previous.number(),
+                            line -> line.trimmed().startsWith("#"),
+                            line -> line.trimmed().startsWith("---"),
+                            line -> line.trimmed().startsWith("..."),
+                            line -> line.trimmed().startsWith("%"),
+                            line -> line.trimmed().startsWith("!!")
+                        ), '{', '}'
+                    ), '[', ']'
                 )
             ),
             true
@@ -245,17 +248,16 @@ final class ReadYamlMapping extends BaseYamlMapping {
                     || trimmed.matches("^" + Pattern.quote(relaxedKey) + ":[ ]*\\|[+-]?$")
                 ) {
                     value = this.significant.nextYamlNode(line);
-                } else if(trimmed.matches(Pattern.quote(relaxedKey) + ":[ ]*\\{}")) {
-                    value = new EmptyYamlMapping(new ReadYamlMapping(
-                        line.number(),
-                        this.all.line(line.number()),
+                } else if(trimmed.matches(Pattern.quote(relaxedKey) + ":\\s*\\{.*$")) {
+                    value = new ReadFlowMapping(
+                        this.getPreviousLine(line),
+                        this.all
+                    );
+                } else if(trimmed.matches(Pattern.quote(relaxedKey) + ":\\s*\\[.*$")) {
+                    value = new ReadFlowSequence(
+                        this.getPreviousLine(line),
                         this.all
-                    ));
-                } else if(trimmed.matches(Pattern.quote(relaxedKey) + ":[ ]*\\[]")) {
-                    value = new EmptyYamlSequence(new ReadYamlSequence(
-                            this.all.line(line.number()),
-                            this.all
-                    ));
+                    );
                 } else if((trimmed.startsWith(tryKey + ":")
                         || trimmed.startsWith("- " + tryKey + ":"))
                         && trimmed.length() > 1
@@ -340,4 +342,20 @@ final class ReadYamlMapping extends BaseYamlMapping {
         final String trim = line.trimmed();
         return trim.startsWith("-") && KEY_PATTERN.matcher(trim).matches();
     }
+
+    /**
+     * Get the line previous to the given one or NullYamlLine if the
+     * given line is the first one.
+     * @param line Given YamlLine.
+     * @return YamlLine previous to it.
+     */
+    private YamlLine getPreviousLine(final YamlLine line) {
+        YamlLine prev;
+        if (line.number() == 0) {
+            prev = new YamlLine.NullYamlLine();
+        } else {
+            prev = this.all.line(line.number() - 1);
+        }
+        return prev;
+    }
 }
diff --git a/src/main/java/com/amihaiemil/eoyaml/ReadYamlSequence.java b/src/main/java/com/amihaiemil/eoyaml/ReadYamlSequence.java
index e4c31fb..20204fc 100644
--- a/src/main/java/com/amihaiemil/eoyaml/ReadYamlSequence.java
+++ b/src/main/java/com/amihaiemil/eoyaml/ReadYamlSequence.java
@@ -32,7 +32,7 @@ import java.util.LinkedList;
 import java.util.List;
 
 /**
- * YamlSequence read from somewhere.
+ * Block-style YamlSequence which has been read from somewhere.
  * @author Mihai Andronache (amihaiemil@gmail.com)
  * @version $Id$
  * @since 1.0.0
@@ -84,14 +84,18 @@ final class ReadYamlSequence extends BaseYamlSequence {
         this.all = lines;
         this.significant = new SameIndentationLevel(
             new WellIndented(
-                new Skip(
-                    lines,
-                    line -> line.number() <= previous.number(),
-                    line -> line.trimmed().startsWith("#"),
-                    line -> line.trimmed().startsWith("---"),
-                    line -> line.trimmed().startsWith("..."),
-                    line -> line.trimmed().startsWith("%"),
-                    line -> line.trimmed().startsWith("!!")
+                new CollapsedFlowLines(
+                    new CollapsedFlowLines(
+                        new Skip(
+                            lines,
+                            line -> line.number() <= previous.number(),
+                            line -> line.trimmed().startsWith("#"),
+                            line -> line.trimmed().startsWith("---"),
+                            line -> line.trimmed().startsWith("..."),
+                            line -> line.trimmed().startsWith("%"),
+                            line -> line.trimmed().startsWith("!!")
+                        ), '{', '}'
+                    ), '[', ']'
                 )
             ),
             false


Test patch:
diff --git a/src/test/java/com/amihaiemil/eoyaml/CollapsedFlowLinesTestCase.java b/src/test/java/com/amihaiemil/eoyaml/CollapsedFlowLinesTestCase.java
new file mode 100644
index 0000000..5ac7335
--- /dev/null
+++ b/src/test/java/com/amihaiemil/eoyaml/CollapsedFlowLinesTestCase.java
@@ -0,0 +1,215 @@
+/**
+ * Copyright (c) 2016-2024, Mihai Emil Andronache
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+package com.amihaiemil.eoyaml;
+
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Unit tests for {@link CollapsedFlowLines}.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @checkstyle ExecutableStatementCount (1000 lines)
+ * @since 8.0.0
+ */
+public final class CollapsedFlowLinesTestCase {
+
+    /**
+     * CollapsedFlowLines can collapse the flow-style sequences within
+     * a block document.
+     */
+    @Test
+    public void collapsesArraysInBlockSequence() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("- [o, d, p]", 0));
+        lines.add(new RtYamlLine("-", 1));
+        lines.add(new RtYamlLine("  [a,", 2));
+        lines.add(new RtYamlLine("\'b]\',", 3));
+        lines.add(new RtYamlLine("c]", 4));
+        lines.add(new RtYamlLine("- otherScalar", 5));
+        lines.add(new RtYamlLine("- other: value", 6));
+        lines.add(new RtYamlLine("- {a: b}", 7));
+        lines.add(new RtYamlLine("- [sss, sad]", 8));
+        lines.add(new RtYamlLine("- [x, ", 9));
+        lines.add(new RtYamlLine("\"y]\",", 10));
+        lines.add(new RtYamlLine("z]", 11));
+
+        final YamlLines collapsed = new CollapsedFlowLines(
+            new AllYamlLines(lines), '[', ']'
+        );
+        for(final YamlLine line : collapsed) {
+            System.out.println(line);
+        }
+        final Iterator<YamlLine> iterator = collapsed.iterator();
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("- [o, d, p]")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("-")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("  [a, 'b]', c]")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("- otherScalar")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("- other: value")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("- {a: b}")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("- [sss, sad]")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("- [x,  \"y]\", z]")
+        );
+    }
+
+    /**
+     * CollapsedFlowLines can collapse the flow-style mappings within
+     * a block document.
+     */
+    @Test
+    public void collapsesMappingsInBlockMapping() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("a: {o: r, d: c, p: [a, b]}", 0));
+        lines.add(new RtYamlLine("b:", 1));
+        lines.add(new RtYamlLine("  {a: i,", 2));
+        lines.add(new RtYamlLine("\'b}\': v,", 3));
+        lines.add(new RtYamlLine("c: w}", 4));
+        lines.add(new RtYamlLine("c: otherScalar", 5));
+        lines.add(new RtYamlLine("d: [other, value]", 6));
+        lines.add(new RtYamlLine("y: {a: b}", 7));
+        lines.add(new RtYamlLine("x: [sss, sad]", 8));
+        lines.add(new RtYamlLine("u: {x: c, ", 9));
+        lines.add(new RtYamlLine("\"y:}\": u,", 10));
+        lines.add(new RtYamlLine("z: [x]}", 11));
+
+        final YamlLines collapsed = new CollapsedFlowLines(
+            new AllYamlLines(lines), '{', '}'
+        );
+        for(final YamlLine line : collapsed) {
+            System.out.println(line);
+        }
+        final Iterator<YamlLine> iterator = collapsed.iterator();
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("a: {o: r, d: c, p: [a, b]}")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("b:")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("  {a: i, 'b}': v, c: w}")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("c: otherScalar")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("d: [other, value]")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("y: {a: b}")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("x: [sss, sad]")
+        );
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo("u: {x: c,  \"y:}\": u, z: [x]}")
+        );
+    }
+
+    /**
+     * CollapsedFlowLines can collapse a whole flow-style sequence document.
+     */
+    @Test
+    public void collapsesWholeFlowArrayDocument() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("[a, b, c", 0));
+        lines.add(new RtYamlLine(",d, e, f, {g: e},", 0));
+        lines.add(new RtYamlLine("xyz, [y, u, u]]", 0));
+
+        final YamlLines collapsed = new CollapsedFlowLines(
+            new AllYamlLines(lines), '[', ']'
+        );
+        final Iterator<YamlLine> iterator = collapsed.iterator();
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo(
+                "[a, b, c ,d, e, f, {g: e}, xyz, [y, u, u]]"
+            )
+        );
+        MatcherAssert.assertThat(iterator.hasNext(), Matchers.is(false));
+    }
+
+    /**
+     * CollapsedFlowLines can collapse a whole flow-style mapping document.
+     */
+    @Test
+    public void collapsesWholeFlowMappingDocument() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("{a: b, c: d, e: f", 0));
+        lines.add(new RtYamlLine(",g: h, i: {g: e},", 0));
+        lines.add(new RtYamlLine("u: [a,v], w: [y, u, u]}", 0));
+
+        final YamlLines collapsed = new CollapsedFlowLines(
+            new AllYamlLines(lines), '{', '}'
+        );
+        final Iterator<YamlLine> iterator = collapsed.iterator();
+        MatcherAssert.assertThat(
+            iterator.next().value(),
+            Matchers.equalTo(
+                "{a: b, c: d, e: f ,g: h, i: {g: e}, u: [a,v], w: [y, u, u]}"
+            )
+        );
+        MatcherAssert.assertThat(iterator.hasNext(), Matchers.is(false));
+    }
+
+}
diff --git a/src/test/java/com/amihaiemil/eoyaml/CollapsedYamlLineTestCase.java b/src/test/java/com/amihaiemil/eoyaml/CollapsedYamlLineTestCase.java
new file mode 100644
index 0000000..50a180a
--- /dev/null
+++ b/src/test/java/com/amihaiemil/eoyaml/CollapsedYamlLineTestCase.java
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c) 2016-2024, Mihai Emil Andronache
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+package com.amihaiemil.eoyaml;
+
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link CollapsedYamlLine}.
+ * @author Mihai Andronache (amihaiemil@gmail.com)
+ * @version $Id$
+ * @checkstyle ExecutableStatementCount (1000 lines)
+ * @since 8.0.0
+ */
+public final class CollapsedYamlLineTestCase {
+
+    /**
+     * The number of the CollapsedYamlLine should be the number of the
+     * first line.
+     */
+    @Test
+    public void returnsNumberOfFirstLine() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("flowSeq: [a,", 4));
+        lines.add(new RtYamlLine("b,c,", 5));
+        lines.add(new RtYamlLine("d]", 6));
+        MatcherAssert.assertThat(
+            new CollapsedYamlLine(lines).number(),
+            Matchers.is(4)
+        );
+    }
+
+    /**
+     * The number of the CollapsedYamlLine should be -1 if there are no lines.
+     */
+    @Test
+    public void returnsNegativeNumberIfNoLines() {
+        MatcherAssert.assertThat(
+            new CollapsedYamlLine(new ArrayList<>()).number(),
+            Matchers.is(-1)
+        );
+    }
+
+    /**
+     * The indentation of the CollapsedYamlLine should be the number of the
+     * first line.
+     */
+    @Test
+    public void returnsIndentationOfFirstLine() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("  - flowSeq: [a,", 4));
+        lines.add(new RtYamlLine("      b,c,", 5));
+        lines.add(new RtYamlLine("      d]", 6));
+        MatcherAssert.assertThat(
+            new CollapsedYamlLine(lines).indentation(),
+            Matchers.is(2)
+        );
+    }
+
+    /**
+     * The indentation of the CollapsedYamlLine should be -1 if there are no
+     * lines.
+     */
+    @Test
+    public void returnsNegativeIndentationIfNoLines() {
+        MatcherAssert.assertThat(
+            new CollapsedYamlLine(new ArrayList<>()).indentation(),
+            Matchers.is(-1)
+        );
+    }
+
+    /**
+     * The value of the CollapsedYamlLine should be all the values of all
+     * the lines concatenated with spaces.
+     */
+    @Test
+    public void returnsCollapsedValue() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("flowSeq: [a,", 4));
+        lines.add(new RtYamlLine("b,c,", 5));
+        lines.add(new RtYamlLine("d]", 6));
+        MatcherAssert.assertThat(
+            new CollapsedYamlLine(lines).value(),
+            Matchers.is("flowSeq: [a, b,c, d]")
+        );
+    }
+
+    /**
+     * The value of the CollapsedYamlLine is identical to the one
+     * of the encapsulated, if there is a single line.
+     */
+    @Test
+    public void returnsCollapsedValueOneLine() {
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("flowSeq: [a, b, c]", 0));
+        MatcherAssert.assertThat(
+            new CollapsedYamlLine(lines).value(),
+            Matchers.is("flowSeq: [a, b, c]")
+        );
+    }
+
+    /**
+     * The value of the CollapsedYamlLine should be empty if there are
+     * no lines.
+     */
+    @Test
+    public void returnsEmptyValueIfNoLines() {
+        MatcherAssert.assertThat(
+            new CollapsedYamlLine(new ArrayList<>()).value(),
+            Matchers.is("")
+        );
+    }
+}
diff --git a/src/test/java/com/amihaiemil/eoyaml/ReadYamlMappingTest.java b/src/test/java/com/amihaiemil/eoyaml/ReadYamlMappingTest.java
index f4e4ada..c4fd3a0 100644
--- a/src/test/java/com/amihaiemil/eoyaml/ReadYamlMappingTest.java
+++ b/src/test/java/com/amihaiemil/eoyaml/ReadYamlMappingTest.java
@@ -262,6 +262,237 @@ public final class ReadYamlMappingTest {
         MatcherAssert.assertThat(
             second, Matchers.instanceOf(YamlSequence.class)
         );
+        MatcherAssert.assertThat(
+            second.string(0), Matchers.equalTo("some")
+        );
+        MatcherAssert.assertThat(
+            second.string(1), Matchers.equalTo("sequence")
+        );
+    }
+
+    /**
+     * ReadYamlMapping can return the flow inline YamlSequence mapped to a
+     * String key after the colon.
+     */
+    @Test
+    public void returnsAfterColonInlineFlowYamlSequenceWithStringKey(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first: somethingElse", 0));
+        lines.add(new RtYamlLine("second: ", 1));
+        lines.add(new RtYamlLine("  [some, sequence]", 2));
+        lines.add(new RtYamlLine("third: something", 3));
+        final YamlMapping map = new ReadYamlMapping(new AllYamlLines(lines));
+        final YamlSequence second = map.yamlSequence("second");
+        MatcherAssert.assertThat(second, Matchers.notNullValue());
+        MatcherAssert.assertThat(
+            second, Matchers.instanceOf(YamlSequence.class)
+        );
+        MatcherAssert.assertThat(
+            second.string(0), Matchers.equalTo("some")
+        );
+        MatcherAssert.assertThat(
+            second.string(1), Matchers.equalTo("sequence")
+        );
+    }
+
+    /**
+     * ReadYamlMapping can return the flow inline YamlSequence mapped to a
+     * String key at the colon.
+     */
+    @Test
+    public void returnsColonInlineFlowYamlSequenceWithStringKey(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first: somethingElse", 0));
+        lines.add(new RtYamlLine("second: [some, sequence]", 1));
+        lines.add(new RtYamlLine("third: something", 2));
+        final YamlMapping map = new ReadYamlMapping(new AllYamlLines(lines));
+        System.out.println(map);
+        final YamlSequence second = map.yamlSequence("second");
+        MatcherAssert.assertThat(second, Matchers.notNullValue());
+        MatcherAssert.assertThat(
+            second, Matchers.instanceOf(YamlSequence.class)
+        );
+        MatcherAssert.assertThat(
+            second.string(0), Matchers.equalTo("some")
+        );
+        MatcherAssert.assertThat(
+            second.string(1), Matchers.equalTo("sequence")
+        );
+    }
+
+    /**
+     * ReadYamlMapping can return the flow multiline YamlSequence mapped to a
+     * String key at the colon.
+     */
+    @Test
+    public void returnsColonMultilineFlowYamlSequenceWithStringKey(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first: somethingElse", 0));
+        lines.add(new RtYamlLine("second: [some,", 1));
+        lines.add(new RtYamlLine("other, {a: b},", 2));
+        lines.add(new RtYamlLine("{c:", 3));
+        lines.add(new RtYamlLine("d},[inner, sequence]],", 4));
+        lines.add(new RtYamlLine("third: something", 5));
+        final YamlMapping map = new ReadYamlMapping(new AllYamlLines(lines));
+        System.out.println(map);
+
+        MatcherAssert.assertThat(map.keys().size(), Matchers.is(3));
+        MatcherAssert.assertThat(map.values().size(), Matchers.is(3));
+
+        final YamlSequence second = map.yamlSequence("second");
+        MatcherAssert.assertThat(second, Matchers.notNullValue());
+        MatcherAssert.assertThat(
+            second, Matchers.instanceOf(YamlSequence.class)
+        );
+        MatcherAssert.assertThat(
+            second.string(0), Matchers.equalTo("some")
+        );
+        MatcherAssert.assertThat(
+            second.string(1), Matchers.equalTo("other")
+        );
+        MatcherAssert.assertThat(
+            second.yamlMapping(2).string("a"), Matchers.equalTo("b")
+        );
+        MatcherAssert.assertThat(
+            second.yamlMapping(3).string("c"), Matchers.equalTo("d")
+        );
+        MatcherAssert.assertThat(
+            second.yamlSequence(4).string(0), Matchers.equalTo("inner")
+        );
+        MatcherAssert.assertThat(
+            second.yamlSequence(4).string(1), Matchers.equalTo("sequence")
+        );
+    }
+
+    /**
+     * ReadYamlMapping can return the flow multiline YamlSequence mapped to a
+     * String key at the colon.
+     */
+    @Test
+    public void returnsColonMultilineFlowYamlMappingWithStringKey(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first: somethingElse", 0));
+        lines.add(new RtYamlLine("second: {some: mapping,", 1));
+        lines.add(new RtYamlLine("c: {a: [c,d]}}", 2));
+        lines.add(new RtYamlLine("third: something", 5));
+        final YamlMapping map = new ReadYamlMapping(new AllYamlLines(lines));
+        System.out.println(map);
+
+        MatcherAssert.assertThat(map.keys().size(), Matchers.is(3));
+        MatcherAssert.assertThat(map.values().size(), Matchers.is(3));
+
+        final YamlMapping second = map.yamlMapping("second");
+        MatcherAssert.assertThat(second, Matchers.notNullValue());
+        MatcherAssert.assertThat(
+            second, Matchers.instanceOf(YamlMapping.class)
+        );
+        MatcherAssert.assertThat(
+            second.string("some"), Matchers.equalTo("mapping")
+        );
+        MatcherAssert.assertThat(
+            second.yamlMapping("c").yamlSequence("a").string(0),
+            Matchers.equalTo("c")
+        );
+        MatcherAssert.assertThat(
+            second.yamlMapping("c").yamlSequence("a").string(1),
+            Matchers.equalTo("d")
+        );
+    }
+
+    /**
+     * ReadYamlMapping can return the empty flow YamlSequence mapped to a
+     * String key at the colon.
+     */
+    @Test
+    public void returnsColonEmptyFlowYamlSequenceWithStringKey(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first: somethingElse", 0));
+        lines.add(new RtYamlLine("second: []", 1));
+        lines.add(new RtYamlLine("third: something", 2));
+        final YamlMapping map = new ReadYamlMapping(new AllYamlLines(lines));
+        System.out.println(map);
+        final YamlSequence second = map.yamlSequence("second");
+        MatcherAssert.assertThat(second, Matchers.notNullValue());
+        MatcherAssert.assertThat(
+            second, Matchers.instanceOf(YamlSequence.class)
+        );
+        MatcherAssert.assertThat(
+            second.size(), Matchers.is(0)
+        );
+    }
+
+    /**
+     * ReadYamlMapping can return the empty flow YamlSequence mapped to a
+     * String key after the colon.
+     */
+    @Test
+    public void returnsAfterColonEmptyFlowYamlSequenceWithStringKey(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first: somethingElse", 0));
+        lines.add(new RtYamlLine("second:", 1));
+        lines.add(new RtYamlLine("  []", 2));
+        lines.add(new RtYamlLine("third: something", 3));
+        final YamlMapping map = new ReadYamlMapping(new AllYamlLines(lines));
+        System.out.println(map);
+        final YamlSequence second = map.yamlSequence("second");
+        MatcherAssert.assertThat(second, Matchers.notNullValue());
+        MatcherAssert.assertThat(
+            second, Matchers.instanceOf(YamlSequence.class)
+        );
+        MatcherAssert.assertThat(
+            second.size(), Matchers.is(0)
+        );
+    }
+
+    /**
+     * ReadYamlMapping can return the empty flow YamlMapping mapped to a
+     * String key at the colon.
+     */
+    @Test
+    public void returnsColonEmptyFlowYamlMappingWithStringKey(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first: somethingElse", 0));
+        lines.add(new RtYamlLine("second: {}", 1));
+        lines.add(new RtYamlLine("third: something", 2));
+        final YamlMapping map = new ReadYamlMapping(new AllYamlLines(lines));
+        System.out.println(map);
+        final YamlMapping second = map.yamlMapping("second");
+        MatcherAssert.assertThat(second, Matchers.notNullValue());
+        MatcherAssert.assertThat(
+            second, Matchers.instanceOf(YamlMapping.class)
+        );
+        MatcherAssert.assertThat(
+            second.keys().size(), Matchers.is(0)
+        );
+        MatcherAssert.assertThat(
+            second.values().size(), Matchers.is(0)
+        );
+    }
+
+    /**
+     * ReadYamlMapping can return the empty flow YamlMapping mapped to a
+     * String key after the colon.
+     */
+    @Test
+    public void returnsAfterColonEmptyFlowYamlMappingWithStringKey(){
+        final List<YamlLine> lines = new ArrayList<>();
+        lines.add(new RtYamlLine("first: somethingElse", 0));
+        lines.add(new RtYamlLine("second:", 1));
+        lines.add(new RtYamlLine("  {}", 2));
+        lines.add(new RtYamlLine("third: something", 3));
+        final YamlMapping map = new ReadYamlMapping(new AllYamlLines(lines));
+        System.out.println(map);
+        final YamlMapping second = map.yamlMapping("second");
+        MatcherAssert.assertThat(second, Matchers.notNullValue());
+        MatcherAssert.assertThat(
+            second, Matchers.instanceOf(YamlMapping.class)
+        );
+        MatcherAssert.assertThat(
+            second.keys().size(), Matchers.is(0)
+        );
+        MatcherAssert.assertThat(
+            second.values().size(), Matchers.is(0)
+        );
     }
 
     /**
@@ -1125,30 +1356,28 @@ public final class ReadYamlMappingTest {
     @Test
     public void dontTurnEmptyMapsAndArraysIntoStrings() {
         final List<YamlLine> lines = new ArrayList<>();
-        lines.add(new RtYamlLine("# A", 0));
-        lines.add(new RtYamlLine("def: {}", 1));
-        lines.add(new RtYamlLine("# B", 2));
-        lines.add(new RtYamlLine("ghi: []", 3));
+        lines.add(new RtYamlLine("def: {}", 0));
+        lines.add(new RtYamlLine("ghi: []", 1));
         final YamlMapping map = new ReadYamlMapping(new AllYamlLines(lines));
         YamlMapping actualMap = map.value("def").asMapping();
-        YamlMapping expectedMap = Yaml.createYamlMappingBuilder().build("A");
+        YamlMapping expectedMap = Yaml.createYamlMappingBuilder().build();
         MatcherAssert.assertThat(
-                actualMap,
-                Matchers.equalTo(expectedMap)
+            actualMap,
+            Matchers.equalTo(expectedMap)
         );
         MatcherAssert.assertThat(
-                actualMap.comment().value(),
-                Matchers.equalTo(expectedMap.comment().value())
+            actualMap.comment().value(),
+            Matchers.equalTo(expectedMap.comment().value())
         );
         YamlSequence actualSeq = map.value("ghi").asSequence();
-        YamlSequence expectedSeq = Yaml.createYamlSequenceBuilder().build("B");
+        YamlSequence expectedSeq = Yaml.createYamlSequenceBuilder().build();
         MatcherAssert.assertThat(
-                actualSeq,
-                Matchers.equalTo(expectedSeq)
+            actualSeq,
+            Matchers.equalTo(expectedSeq)
         );
         MatcherAssert.assertThat(
-                actualSeq.comment().value(),
-                Matchers.equalTo(expectedSeq.comment().value())
+            actualSeq.comment().value(),
+            Matchers.equalTo(expectedSeq.comment().value())
         );
     }