"1.2025.0" has "%newline()" bug (older version 1.2024.7 was working)

0 votes
asked Jan 21 in Bug by kirchsth (7,480 points)

maybe related to New Syntax Error in existing Sequence Diagrams (since 1.2025.0??) - PlantUML Q&A, but I tested it online and with "1.2025.1beta5"

In older versions following script worked without any problems:
e.g. I executed it locally with "plantuml-mit-1.2024.7"

@startuml

note as N
bla bla
end note

!function $createNoteM()
  !return "note as M" + %newline() + "this should work too in " + %version() + %newline() + "end note" + %newline()
!endfunction

$createNoteM()

' works with plantuml-mit-1.2024.7.jar
' fails with plantuml-mit-SNAPSHOT-1.2025.1beta5.jar

@enduml


I tested it locally with 1.2025.0, 1.2025.1beta5 and online. (link) None are working 



Can you check it. 
Thank you and best regards
Helmut

2 Answers

0 votes
answered Jan 21 by plantuml (296,000 points)
Thank you, Helmut.  

This issue appears to be a regression caused by the preliminary work related to "Suppression of \n management and Multiline text blocks proposal"  https://github.com/plantuml/plantuml/issues/1985

In principle, these preliminary changes should not have introduced any regressions, but clearly, that’s not the case here.  

We’ll investigate this further. Apologies for the inconvenience, and thank you for bringing it to our attention!
0 votes
answered Jan 21 by plantuml (296,000 points)

After investigating, we identified that the root cause lies in an ambiguity in the behavior of %newline(). Specifically, %newline() serves two conflicting purposes:  
1. It inserts a line break in the postprocessed source text, which can affect the syntax and validity of the generated script.  
2. It also controls visual line breaks in the rendered diagrams.  

This dual purpose creates confusion and potential inconsistencies, especially in cases like yours where %newline() is expected to generate valid source code during preprocessing.  

Proposed Solution:  
To resolve this ambiguity, we are considering introducing a new function, %breakline().  
- %breakline() would strictly insert a new line in the postprocessed source text, ensuring valid syntax generation when building PlantUML scripts dynamically.  
- %newline() would retain its current behavior for visual line breaks.  

We believe this separation will make the language more predictable for users.  

For example, consider this diagram:  

@startuml
!function $message()
  !return "this is" + %newline() + "on several lines"
!endfunction
Alice -> Bob: $message()
@enduml

In this case %newline() inserts a new line in the text sent to the rendering engine.  

Here’s your original example modified to demonstrate how we’re considering addressing this issue:  

@startuml

note as N
bla bla
end note

!function $createNoteM()
  !return "note as M" + %breakline() +
         "this should work too in " + %version() + %breakline() +
         "end note" + %breakline()
!endfunction

$createNoteM()

@enduml

Here, %breakline() would specifically insert a new line in the preprocessed source text, ensuring the generated script is valid.   

We fully acknowledge that this approach would introduce a breaking change for users who rely on %newline() for source preprocessing. We deeply regret the inconvenience this may cause, and we’re carefully evaluating the best way to manage this transition. Your feedback helps us improve and make the language more consistent in the long run.  

Thank you again for your report, and please accept our apologies for the disruption. We’ll keep you updated on the resolution.  

commented Jan 22 by kirchsth (7,480 points)

Thank you for the check, 

a) Could it be that there is another issue too? 
 Eg. I copied a procedure and a function  (parts are simplified) from my C4.puml. And both are working with the latest version too. 




b) If a new %breakline() is introduced and I understand you correctly I would have to replace ~95% of the %newline() with %breakline() like in the C4* function/procedure. Or did I understand it wrong? 

!function $elementTagSkinparams($element, $tagStereo, $bgColor)
  !$elementSkin = "skinparam " + $element + "<<" + $tagStereo + ">> {" + %newline()
  !if ($bgColor != "")
    !$elementSkin = $elementSkin + "    BackgroundColor " + $bgColor + %newline()
  !endif
  !$elementSkin = $elementSkin + "}" + %newline()
  !return $elementSkin
!endfunction

c) for your message sample we could use a notation like too:
  c1) %newline() remains part  of the intermediate file, and is processed in the second "phase"

@startuml
!function $message()
  !return "this is %newline() on several lines"
!endfunction
Alice -> Bob: $message()
@enduml 


c2) %newline() is replaced in the phase 1 and stored in the intermediate file, and in the second phase the already converted newline is reused

@startuml
!function $message()
  !return "this is " + %newline() + "on several lines"
!endfunction
Alice -> Bob: $message()
@enduml


BR Helmut

commented Jan 22 by plantuml (296,000 points)

The good news is that during our non-regression tests, we identified this issue with C4.  
As a result, we implemented a very specific workaround for dynamically constructing skinparam to ensure it still works with %newline(), even though, in theory, %breakline() should have been used.  

This workaround is essentially a hack to avoid having to modify the standard library.  
Therefore, dynamic skinparam will work with either %newline() or %breakline().  
As I mentioned earlier, we’re trying to minimize the impact on users.  

That said, this is a very specific use case, where some specific code has been written just for that case.

Regarding your examples, from the preprocessor's perspective, there’s no difference between these two lines:  

!return "this is %newline() on several lines"
!return "this is " + %newline() + "on several lines"

Both produce exactly the same result. Of course, we could decide both would produce different results, but I think it would bring even more confusion :-)

I understand that the distinction between %newline() and %breakline() might seem artificial, but consider the following example:  

@startuml
Alice -> Bob : this is %newline() on several lines
return ok
@enduml

Now, imagine we want to write a function $exchange() that returns this exchange:  

!function $message()
  !return "Alice -> Bob : this is%newline()on several lines%breakline()return ok"
!endfunction

All of these variations will produce the same output:  

!return "Alice -> Bob : this is" + %newline() + "on several lines" + %breakline() + "return ok"
!return "Alice -> Bob : this is%newline()on several lines" + %breakline() + "return ok"

Here, it becomes evident that the semantics of the two functions %newline() and %breakline() are different.  

Previously, and this was more by chance, we managed to make things work with a mix of \n and \\n because the interpretations of line breaks were processed in different stages.  
But frankly, that was just a coincidence of the implementation.  

Addressing Your Question:

b) If a new %breakline() is introduced and I understand you correctly, I would have to replace ~95% of the %newline() with %breakline() like in the C4* function/procedure. Or did I understand it wrong?**  

In theory, you are correct, but we implemented a special case for skinparam.  

Despite this, I do acknowledge that there will be impacts. However, after exploring this issue from every possible angle, it seems unavoidable that we need both methods: %breakline() and %newline().  

Our goal remains to minimize the migration work required for our users as much as possible, so I’m open to any suggestions or feedback you might have.  

commented Jan 22 by kirchsth (7,480 points)

I tried some calls (details see below) and based on that I see following rules:
1) If a term is used directly in a eg. "message" then PlantUML uses a different implementation/calculation compared with a variable or a return value of a function:



2) My assumption is that "%breakline()" can be used in combination with variables/functions.
    But in context of normal "messages" it is useless (or at least should be avoided that it remains readable)
I couldn't try it online, I think it has no %breakline() support

3) I found no way that I can create the message $multipleM() without a variable. Did I oversee an escape character?



4) %newline() has a special handling in the parser, eg. $multipleM() is not (correct?) parsed


 

commented Jan 22 by plantuml (296,000 points)

Many thanks for your examples!

There are definitely some issues and inconsistencies here; we still need to think this through.

My assumption is that "%breakline()" can be used in combination with variables/functions.
    But in context of normal "messages" it is useless (or at least should be avoided that it remains readable)

Indeed!

I couldn't try it online, I think it has no %breakline() support

It's not implemented yet, but will be soon :-)

commented Jan 23 by plantuml (296,000 points)

We have released version 1.2025.1beta6 online.

The breakline function is now implemented.

This update slightly changes the behavior of %newline(), but we believe it is now more consistent. (Of course, we’re not 100% certain, so we’re happy to discuss it further!  :-) )

Before diving into %newline(), I suggest we start by focusing on the following example:

@startuml
!function $multipleM()
  !return "MMM"
!endfunction

Bob -> Alice : 1 "XXX + $multipleM() + XXX"

!$mixed0 = "XXX + $multipleM() + XXX"
Bob -> Alice : 2 $mixed0

!$mixed1 = "XXX" + $multipleM() + "XXX"
Bob -> Alice : 3 $mixed1

!function combined0()
  !return "XXX + $multipleM()+ XXX"
!endfunction

!function combined1()
  !return "XXX" + $multipleM() + "XXX"
!endfunction

Bob -> Alice : 4 combined0()

Bob -> Alice : 5 combined1()

@enduml

image

Link

We believe this behavior is consistent, but we're open to discussion

commented Jan 24 by kirchsth (7,480 points)
edited Jan 24 by kirchsth

Thank you for the changes: 

a) related to the function replacement in the sequence diagram: your "explicit calculated" samples are consistent.
But with the implicit there are still inconsistencies between %newline() and $multipleM():
    - combination with (space) delimiters: %newline() and $multipleM() are replace -> OK  
    - without (space) delimiters: %newline() is replaced , $multipleM() is not replaced -> not consistent (It could be the most consistent way that it it is NOT replaced in both cases)
       -  Problem: if all variable are replaced without (space) delimiters too the translation is not unambiguous (E.g. how convert the message "ABC", if the variables are A=1, AB=2, BC=3, C=4? Is the result "13" or "24"?) 

   Idea: - If a function should be replaced without a (space) delimiter, a variable has to be used

     - another replace approach could be the "%eval()" function.
        (==> means %eval() has a specific status that it is "replaced" without (space) delimiters too; e.g.  "XXX%eval(%newline())XXX") but in this case "%eval()" needs a special implementation

@startuml
!function $multipleM()
  !return "MMM"
!endfunction


Bob -> Alice : replace, based on space delimiter - XXX $multipleM()XXX

Bob -> Alice : remain, based on missing delimiter - XXX$multipleM()XXX

Bob -> Alice : replaced based on "call_user_func" function - XXX%call_user_func($multipleM)XXX

' WRONG ??? / NEW
' maybe better with eval(); But at the moment only numbers are supported? 
Bob -> Alice : replaced based on "eval" function - XXX%eval($multipleM())XXX

=======

Bob -> Alice : should be replaced (space delimiter) - XXX %newline() XXX

' WRONG ????
Bob -> Alice : should be not replaced (if same impl. like $multipleM()) - XXX%newline()XXX

' internal fucntions are not supported atm
' Bob -> Alice : replaced based on "call_user_func" function - XXX%call_user_func(%newline)XXX

' SYNTAX ERROR
' Bob -> Alice : should be replaced (via eval) - XXX%eval(%newline())XXX

====== 

!$replacedM = "XXX" + $multipleM() + "XXX"
Bob -> Alice : replaced based on var - $replacedM

!replacedN = "XXX" + %newline() + "XXX"
Bob -> Alice : replaced based on var - $replacedN
@enduml



b) related to %breakline(): Maybe a second function name is obsolete (like the `-` operator and the `-` sign, here we use one symbol for 2 meanings too). I think, the context  could be enough. I will prepare a sample in a following comment.

commented Jan 25 by plantuml (296,000 points)

a) Thank you for your detailed feedback and thoughtful suggestions regarding the handling of function.  

After reviewing the points you raised, we've made changes to ensure consistency, particularly for cases where functions like $multipleM() or %newline() are used without delimiters.  

Previously, there was an inconsistency: %newline() was replaced even without delimiters, while $multipleM() was not.  
Now, with release 1.2025.1beta7, both %newline() and $multipleM() are treated consistently. They are replaced even if they are used without delimiters.  

@startuml
!function $multipleM()
 !return "MMM"
!endfunction

Bob -> Alice : 1 XXX $multipleM()XXX
Bob -> Alice : 2 XXX$multipleM()XXX
Bob -> Alice : 3 XXX %newline()XXX
Bob -> Alice : 4 XXX%newline()XXX
@enduml

You raised an excellent point about ambiguous cases where variables with overlapping names could cause confusion (e.g., A=1, AB=2, BC=3, C=4).
Right now, you can have:

@startuml
' BTW, that's why we suggest using variable names starting with $
!A=1
!AB=2
!BC=3

Bob -> Alice : ABC
Bob -> Alice : %get_variable_value("AB")C
Bob -> Alice : A%get_variable_value("BC")
@enduml


b) We also thought the same at the beginning!  

However, the issue arises in scenarios where the context isn't enough to differentiate between %newline() and %breakline(). Let me illustrate this with an example:  

@startuml
!function $message1()
  !return "Alice -> Bob : foo1" + %newline() + "return ok1"
!endfunction

!function $message2()
  !return "Alice -> Bob : foo2" + %breakline() + "return ok2"
!endfunction

$message1()

====

$message2()
@enduml

In this case, $message1() and $message2() are supposed to generate different types of behavior:


If there were only one function that tried to handle both contexts, it wouldn't be possible to reliably achieve the desired distinction in this type of usage. By having two separate functions, %newline() and %breakline(), the behavior becomes explicit and predictable, ensuring there is no ambiguity in the result.  

Let us know what you think!

commented Jan 25 by kirchsth (7,480 points)
edited Jan 25 by kirchsth

thank you for the fast implementation

a) you are right: your version with automatic replacement is the more intuitive one
    (is was a 50:50 decision on my side)

You are right my ABC sample was very hypothetically but I tried to cover all scenarios
Maybe you extend the documentation with
   - the "$ advantages" are: automatic replacement works in all scenarios, supported by online/many editors ...
   - related to "%": it is reserved for builtin functions



With the new implementation, it could be very hard to write %newline() without replacement.
Therefor I suggest that
1) the escape character \ supports % and $ too  (\\..\; \n..produces new line; \%..% ->  \%newline()..%newline(), ... )
or 2) add a function (like %raw($mulltipleM()) ) that the argument of the raw function is not replaced.
or 3)  wink use the existing %chr(36) and %chr(37) and extend the documentation with the possibility 
or 4)  wink use a variable like !$msg="XXX%newline()XXX" and A->B:$msg and extend the documentation with the possibility 


b) you are right the 2 terms %newline() and %breakline() are necessary.
     (luckily, you checked that C4-Stdlib can be reused without any changes. Thank you for your effort)

c) sorry I have a finding with the new implementation: %newline() in table is not working

@startuml
!function $myMultiTableOK()
   !return "| a | b | abc\n def |" +%breakline()+ "| x | y | z |"
!endfunction

!function $myMultiTableNOK()
   !return "| a | b | abc" + %newline() + " def |" +%breakline()+ "| x | y | z |"
!endfunction

note as N
$myMultiTableOK()

$myMultiTableNOK()
end note
@enduml




d) with the new %newline() implementation, can you continue the next line with the same format too? (Or has compatibility the higher priority?)

@startuml
A -> B : <b>only first line%newline()is bold</b>
@enduml


BR 
Helmut

commented Jan 26 by plantuml (296,000 points)

a) We have created a new method: %percent().

Regarding the backslash, the main idea is to stop using it as an escape character.
We have started drafting some explanations here: https://plantuml.com/newline.
It's still a work in progress!

c) Thank you for the feedback, it has been fixed in V1.2025.1beta8.

d) Currently, formatting is reset with %newline().
I agree that this is not ideal.
It’s not easy to fix immediately, but I’m willing to consider this as a bug.
However, I suggest postponing this issue until after version V1.2025.3.

I hope this is not a big issue for you

commented Jan 26 by kirchsth (7,480 points)
a) my simplest (and still consistent) solution would be "4) updating documentation and using a variable".
    if you have %percent() then I think you need a $dollar() too,  

c) thank you

d) formatting is not a big issue for me (I have already the workaround with my own implementation in C4). A fix in one of the next versions is fine for me
commented Jan 26 by plantuml (296,000 points)

a) since buitin functions starts with %, we just added the %dollar() function.

Hope this helps!

commented Jan 30 by kirchsth (7,480 points)

related to d) only for documentation, there is an old format \n issue 
HTML tag based format lost after \n - PlantUML Q&A 

...