Jaws.java
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/paypal
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*
* Original Author: Arnaud Roques
*
*
*/
package net.sourceforge.plantuml.jaws;
import java.util.List;
import net.sourceforge.plantuml.text.StringLocated;
import net.sourceforge.plantuml.utils.LineLocation;
public class Jaws {
public static final boolean TRACE = false;
public static final char BLOCK_E1_NEWLINE = '\uE100';
public static final char BLOCK_E1_NEWLINE_LEFT_ALIGN = '\uE101';
public static final char BLOCK_E1_NEWLINE_RIGHT_ALIGN = '\uE102';
public static final char BLOCK_E1_BREAKLINE = '\uE103';
public static final char BLOCK_E1_REAL_BACKSLASH = '\uE110';
public static final char BLOCK_E1_REAL_TABULATION = '\uE111';
public static final char BLOCK_E1_INVISIBLE_QUOTE = '\uE121';
public static final char BLOCK_E1_START_TEXTBLOCK = '\uE122';
public static final char BLOCK_E1_END_TEXTBLOCK = '\uE123';
public static void mutateExpands1(List<StringLocated> tmp) {
mutateExpandsBreakline(tmp);
if (JawsFlags.PARSE_NEW_MULTILINE_TRIPLE_MARKS)
mergeTripleMarkBlocks(tmp);
}
private static void mutateExpandsBreakline(List<StringLocated> tmp) {
for (int i = 0; i < tmp.size(); i++) {
final StringLocated sl = tmp.get(i);
final String s = sl.getString();
// Fast path: no breakline -> nothing to do for this entry.
if (s.indexOf(BLOCK_E1_BREAKLINE) == -1)
continue;
// Split the line at every BLOCK_E1_BREAKLINE that is not inside a {{...}}
// embedded block. Write the first piece in place at index i, then insert the
// remaining pieces right after.
final LineLocation location = sl.getLocation();
final String preprocessorError = sl.getPreprocessorError();
final int len = s.length();
final StringBuilder pending = new StringBuilder(len);
int level = 0;
boolean firstWritten = false;
int inserted = 0;
for (int j = 0; j < len; j++) {
final char ch = s.charAt(j);
if (ch == '{' && j + 1 < len && s.charAt(j + 1) == '{')
level++;
else if (ch == '}' && j + 1 < len && s.charAt(j + 1) == '}')
level--;
if (level > 0) {
pending.append(ch);
} else if (ch == BLOCK_E1_BREAKLINE) {
final StringLocated piece = new StringLocated(pending.toString(), location, preprocessorError);
if (firstWritten == false) {
tmp.set(i, piece);
firstWritten = true;
} else {
tmp.add(i + 1 + inserted, piece);
inserted++;
}
pending.setLength(0);
} else {
pending.append(ch);
}
}
// Tail piece (always emitted, even when empty, to match the legacy behaviour).
final StringLocated tail = new StringLocated(pending.toString(), location, preprocessorError);
if (firstWritten == false) {
// No actual split happened (the BLOCK_E1_BREAKLINE chars were all inside
// {{...}} blocks). Replace in place if the content is materially different.
tmp.set(i, tail);
} else {
tmp.add(i + 1 + inserted, tail);
inserted++;
}
i += inserted;
}
}
private static void mergeTripleMarkBlocks(List<StringLocated> tmp) {
// in-place expansion of triple-mark
// separators ('''/!!!/""").
// Common case (no triple-mark on a line): the entry is left untouched.
// When a triple-mark opens on line i, we consume input lines until the closing
// triple-mark and merge them all into a single output line at write index w.
final int n = tmp.size();
int w = 0;
for (int i = 0; i < n; i++) {
final StringLocated current = tmp.get(i);
final int x = current.findMultilineTripleSeparator();
if (x == -1) {
if (w != i)
tmp.set(w, current);
w++;
continue;
}
// Opening triple-mark found. Build the merged line in a single StringBuilder
// to avoid the cascade of StringLocated.append() that allocate at each step.
final StringLocated[] head = current.splitAtTripleSeparator(x);
final StringLocated openLine = head[0];
final String openTail = head[1].getString();
final int headerLength = openLine.length() + 3;
final MultilinesBloc block = new MultilinesBloc(headerLength, openTail);
String closeTail = "";
while (true) {
i++;
final StringLocated mid = tmp.get(i);
final int xMid = mid.findMultilineTripleSeparator();
if (xMid == -1) {
block.add(mid.jawsHideBackslash().getString());
} else {
final StringLocated[] tail = mid.splitAtTripleSeparator(xMid);
block.add(tail[0].getString());
closeTail = tail[1].getString();
break;
}
}
final StringBuilder sb = new StringBuilder(
openLine.length() + 2 + block.getMerged().length() + closeTail.length());
sb.append(openLine.getString());
sb.append(BLOCK_E1_INVISIBLE_QUOTE);
sb.append(block.getMerged());
sb.append(BLOCK_E1_INVISIBLE_QUOTE);
sb.append(closeTail);
tmp.set(w++, new StringLocated(sb.toString(), openLine.getLocation(), openLine.getPreprocessorError()));
}
// Drop the now-unused tail in one shot.
if (w < n)
tmp.subList(w, n).clear();
}
}