| #include "scanscalar.h" | |
| #include <algorithm> | |
| #include "exp.h" | |
| #include "regeximpl.h" | |
| #include "stream.h" | |
| #include "yaml-cpp/exceptions.h" // IWYU pragma: keep | |
| namespace YAML { | |
| // ScanScalar | |
| // . This is where the scalar magic happens. | |
| // | |
| // . We do the scanning in three phases: | |
| // 1. Scan until newline | |
| // 2. Eat newline | |
| // 3. Scan leading blanks. | |
| // | |
| // . Depending on the parameters given, we store or stop | |
| // and different places in the above flow. | |
| std::string ScanScalar(Stream& INPUT, ScanScalarParams& params) { | |
| bool foundNonEmptyLine = false; | |
| bool pastOpeningBreak = (params.fold == FOLD_FLOW); | |
| bool emptyLine = false, moreIndented = false; | |
| int foldedNewlineCount = 0; | |
| bool foldedNewlineStartedMoreIndented = false; | |
| std::size_t lastEscapedChar = std::string::npos; | |
| std::string scalar; | |
| params.leadingSpaces = false; | |
| if (!params.end) { | |
| params.end = &Exp::Empty(); | |
| } | |
| while (INPUT) { | |
| // ******************************** | |
| // Phase #1: scan until line ending | |
| std::size_t lastNonWhitespaceChar = scalar.size(); | |
| bool escapedNewline = false; | |
| while (!params.end->Matches(INPUT) && !Exp::Break().Matches(INPUT)) { | |
| if (!INPUT) { | |
| break; | |
| } | |
| // document indicator? | |
| if (INPUT.column() == 0 && Exp::DocIndicator().Matches(INPUT)) { | |
| if (params.onDocIndicator == BREAK) { | |
| break; | |
| } | |
| if (params.onDocIndicator == THROW) { | |
| throw ParserException(INPUT.mark(), ErrorMsg::DOC_IN_SCALAR); | |
| } | |
| } | |
| foundNonEmptyLine = true; | |
| pastOpeningBreak = true; | |
| // escaped newline? (only if we're escaping on slash) | |
| if (params.escape == '\\' && Exp::EscBreak().Matches(INPUT)) { | |
| // eat escape character and get out (but preserve trailing whitespace!) | |
| INPUT.get(); | |
| lastNonWhitespaceChar = scalar.size(); | |
| lastEscapedChar = scalar.size(); | |
| escapedNewline = true; | |
| break; | |
| } | |
| // escape this? | |
| if (INPUT.peek() == params.escape) { | |
| scalar += Exp::Escape(INPUT); | |
| lastNonWhitespaceChar = scalar.size(); | |
| lastEscapedChar = scalar.size(); | |
| continue; | |
| } | |
| // otherwise, just add the damn character | |
| char ch = INPUT.get(); | |
| scalar += ch; | |
| if (ch != ' ' && ch != '\t') { | |
| lastNonWhitespaceChar = scalar.size(); | |
| } | |
| } | |
| // eof? if we're looking to eat something, then we throw | |
| if (!INPUT) { | |
| if (params.eatEnd) { | |
| throw ParserException(INPUT.mark(), ErrorMsg::EOF_IN_SCALAR); | |
| } | |
| break; | |
| } | |
| // doc indicator? | |
| if (params.onDocIndicator == BREAK && INPUT.column() == 0 && | |
| Exp::DocIndicator().Matches(INPUT)) { | |
| break; | |
| } | |
| // are we done via character match? | |
| int n = params.end->Match(INPUT); | |
| if (n >= 0) { | |
| if (params.eatEnd) { | |
| INPUT.eat(n); | |
| } | |
| break; | |
| } | |
| // do we remove trailing whitespace? | |
| if (params.fold == FOLD_FLOW) | |
| scalar.erase(lastNonWhitespaceChar); | |
| // ******************************** | |
| // Phase #2: eat line ending | |
| n = Exp::Break().Match(INPUT); | |
| INPUT.eat(n); | |
| // ******************************** | |
| // Phase #3: scan initial spaces | |
| // first the required indentation | |
| while (INPUT.peek() == ' ' && | |
| (INPUT.column() < params.indent || | |
| (params.detectIndent && !foundNonEmptyLine)) && | |
| !params.end->Matches(INPUT)) { | |
| INPUT.eat(1); | |
| } | |
| // update indent if we're auto-detecting | |
| if (params.detectIndent && !foundNonEmptyLine) { | |
| params.indent = std::max(params.indent, INPUT.column()); | |
| } | |
| // and then the rest of the whitespace | |
| while (Exp::Blank().Matches(INPUT)) { | |
| // we check for tabs that masquerade as indentation | |
| if (INPUT.peek() == '\t' && INPUT.column() < params.indent && | |
| params.onTabInIndentation == THROW) { | |
| throw ParserException(INPUT.mark(), ErrorMsg::TAB_IN_INDENTATION); | |
| } | |
| if (!params.eatLeadingWhitespace) { | |
| break; | |
| } | |
| if (params.end->Matches(INPUT)) { | |
| break; | |
| } | |
| INPUT.eat(1); | |
| } | |
| // was this an empty line? | |
| bool nextEmptyLine = Exp::Break().Matches(INPUT); | |
| bool nextMoreIndented = Exp::Blank().Matches(INPUT); | |
| if (params.fold == FOLD_BLOCK && foldedNewlineCount == 0 && nextEmptyLine) | |
| foldedNewlineStartedMoreIndented = moreIndented; | |
| // for block scalars, we always start with a newline, so we should ignore it | |
| // (not fold or keep) | |
| if (pastOpeningBreak) { | |
| switch (params.fold) { | |
| case DONT_FOLD: | |
| scalar += "\n"; | |
| break; | |
| case FOLD_BLOCK: | |
| if (!emptyLine && !nextEmptyLine && !moreIndented && | |
| !nextMoreIndented && INPUT.column() >= params.indent) { | |
| scalar += " "; | |
| } else if (nextEmptyLine) { | |
| foldedNewlineCount++; | |
| } else { | |
| scalar += "\n"; | |
| } | |
| if (!nextEmptyLine && foldedNewlineCount > 0) { | |
| scalar += std::string(foldedNewlineCount - 1, '\n'); | |
| if (foldedNewlineStartedMoreIndented || | |
| nextMoreIndented | !foundNonEmptyLine) { | |
| scalar += "\n"; | |
| } | |
| foldedNewlineCount = 0; | |
| } | |
| break; | |
| case FOLD_FLOW: | |
| if (nextEmptyLine) { | |
| scalar += "\n"; | |
| } else if (!emptyLine && !escapedNewline) { | |
| scalar += " "; | |
| } | |
| break; | |
| } | |
| } | |
| emptyLine = nextEmptyLine; | |
| moreIndented = nextMoreIndented; | |
| pastOpeningBreak = true; | |
| // are we done via indentation? | |
| if (!emptyLine && INPUT.column() < params.indent) { | |
| params.leadingSpaces = true; | |
| break; | |
| } | |
| } | |
| // post-processing | |
| if (params.trimTrailingSpaces) { | |
| std::size_t pos = scalar.find_last_not_of(" \t"); | |
| if (lastEscapedChar != std::string::npos) { | |
| if (pos < lastEscapedChar || pos == std::string::npos) { | |
| pos = lastEscapedChar; | |
| } | |
| } | |
| if (pos < scalar.size()) { | |
| scalar.erase(pos + 1); | |
| } | |
| } | |
| switch (params.chomp) { | |
| case CLIP: { | |
| std::size_t pos = scalar.find_last_not_of('\n'); | |
| if (lastEscapedChar != std::string::npos) { | |
| if (pos < lastEscapedChar || pos == std::string::npos) { | |
| pos = lastEscapedChar; | |
| } | |
| } | |
| if (pos == std::string::npos) { | |
| scalar.erase(); | |
| } else if (pos + 1 < scalar.size()) { | |
| scalar.erase(pos + 2); | |
| } | |
| } break; | |
| case STRIP: { | |
| std::size_t pos = scalar.find_last_not_of('\n'); | |
| if (lastEscapedChar != std::string::npos) { | |
| if (pos < lastEscapedChar || pos == std::string::npos) { | |
| pos = lastEscapedChar; | |
| } | |
| } | |
| if (pos == std::string::npos) { | |
| scalar.erase(); | |
| } else if (pos < scalar.size()) { | |
| scalar.erase(pos + 1); | |
| } | |
| } break; | |
| default: | |
| break; | |
| } | |
| return scalar; | |
| } | |
| } // namespace YAML |