diff --git a/xwiki-rendering-integration-tests/src/test/resources/simple/macros/macro38.test b/xwiki-rendering-integration-tests/src/test/resources/simple/macros/macro38.test new file mode 100644 index 0000000000..227ef7d518 --- /dev/null +++ b/xwiki-rendering-integration-tests/src/test/resources/simple/macros/macro38.test @@ -0,0 +1,88 @@ +.runTransformations +.#----------------------------------------------------- +.# A non-empty format after a macro must keep the macro inline without wrapping it in the format +.# (XRENDERING-766): see test1, test3 and test5. A format before a macro still wraps it (test5). +.# An *empty* format right after a macro (test2, test4) carries no content: the format is dropped and the +.# macro is emitted as a standalone block, exactly as if the format were not there. The dropped format must +.# not leak into the following block either - test2 is followed by "Padding", which must stay unformatted. +.# The xwiki/2.0 input therefore does not round-trip (the empty formats disappear), so input and output are +.# specified separately. +.#----------------------------------------------------- +.input|xwiki/2.0 +.#----------------------------------------------------- +{{testsimpleinlinemacro/}}(% id="test1" %)hi + +{{testsimpleinlinemacro/}}(% id="test2" %) + +Padding + +{{testsimpleinlinemacro/}}(% id="test3" %){{testsimpleinlinemacro/}} + +{{testsimpleinlinemacro/}}(% id="test4" %) + +(% id="test5" %){{testsimpleinlinemacro/}} +.#----------------------------------------------------- +.expect|event/1.0 +.#----------------------------------------------------- +beginDocument +beginParagraph +beginMacroMarkerInline [testsimpleinlinemacro] [] +onWord [simpleinlinemacro2] +endMacroMarkerInline [testsimpleinlinemacro] [] +beginFormat [NONE] [[id]=[test1]] +onWord [hi] +endFormat [NONE] [[id]=[test1]] +endParagraph +beginMacroMarkerStandalone [testsimpleinlinemacro] [] +beginParagraph +onWord [simpleinlinemacro3] +endParagraph +endMacroMarkerStandalone [testsimpleinlinemacro] [] +beginParagraph +onWord [Padding] +endParagraph +beginParagraph +beginMacroMarkerInline [testsimpleinlinemacro] [] +onWord [simpleinlinemacro4] +endMacroMarkerInline [testsimpleinlinemacro] [] +beginFormat [NONE] [[id]=[test3]] +beginMacroMarkerInline [testsimpleinlinemacro] [] +onWord [simpleinlinemacro5] +endMacroMarkerInline [testsimpleinlinemacro] [] +endFormat [NONE] [[id]=[test3]] +endParagraph +beginMacroMarkerStandalone [testsimpleinlinemacro] [] +beginParagraph +onWord [simpleinlinemacro6] +endParagraph +endMacroMarkerStandalone [testsimpleinlinemacro] [] +beginParagraph +beginFormat [NONE] [[id]=[test5]] +beginMacroMarkerInline [testsimpleinlinemacro] [] +onWord [simpleinlinemacro7] +endMacroMarkerInline [testsimpleinlinemacro] [] +endFormat [NONE] [[id]=[test5]] +endParagraph +endDocument +.#----------------------------------------------------- +.expect|xwiki/2.0 +.#----------------------------------------------------- +{{testsimpleinlinemacro/}}(% id="test1" %)hi + +{{testsimpleinlinemacro/}} + +Padding + +{{testsimpleinlinemacro/}}(% id="test3" %){{testsimpleinlinemacro/}} + +{{testsimpleinlinemacro/}} + +(% id="test5" %){{testsimpleinlinemacro/}} +.#----------------------------------------------------- +.expect|annotatedxhtml/1.0 +.#----------------------------------------------------- +
simpleinlinemacro2hi
simpleinlinemacro3
Padding
simpleinlinemacro4simpleinlinemacro5
simpleinlinemacro6
simpleinlinemacro7
+.#----------------------------------------------------- +.input|xhtml/1.0 +.#----------------------------------------------------- +simpleinlinemacro2hi
simpleinlinemacro3
Padding
simpleinlinemacro4simpleinlinemacro5
simpleinlinemacro6
simpleinlinemacro7
diff --git a/xwiki-rendering-integration-tests/src/test/resources/simple/macros/macro39.test b/xwiki-rendering-integration-tests/src/test/resources/simple/macros/macro39.test new file mode 100644 index 0000000000..d9bafc154c --- /dev/null +++ b/xwiki-rendering-integration-tests/src/test/resources/simple/macros/macro39.test @@ -0,0 +1,30 @@ +.#----------------------------------------------------- +.inputexpect|xwiki/2.1 +.# Stored XWiki syntax of an inline macro (here a mention) followed by CKEditor's "xwiki-macro-inline-enforcer". +.# CKEditor appends this span (holding a non-breaking space) after an inline macro so the rendering round-trip of +.# the macro content keeps the macro inline instead of turning it into a block; CKEditor removes the span again +.# afterwards. The macro must therefore be emitted *before* the enforcer format and must never be wrapped inside +.# it - otherwise removing the enforcer span would delete the macro too (XWIKI-21973 / XRENDERING-766). +.# NOTE: the input line below ends with a space (the enforcer content); it is preserved as the format content so the +.# enforcer format is non-empty. +.#----------------------------------------------------- +{{mention reference="XWiki.Admin" style="FULL_NAME" anchor="XWiki-Admin-7qc"/}}(% id="xwiki-macro-inline-enforcer" %) +.#----------------------------------------------------- +.expect|event/1.0 +.#----------------------------------------------------- +beginDocument +beginParagraph +onMacroInline [mention] [reference=XWiki.Admin|style=FULL_NAME|anchor=XWiki-Admin-7qc] +beginFormat [NONE] [[id]=[xwiki-macro-inline-enforcer]] +onSpace +endFormat [NONE] [[id]=[xwiki-macro-inline-enforcer]] +endParagraph +endDocument +.#----------------------------------------------------- +.expect|annotatedxhtml/1.0 +.#----------------------------------------------------- ++.#----------------------------------------------------- +.input|xhtml/1.0 +.#----------------------------------------------------- +
diff --git a/xwiki-rendering-integration-tests/src/test/resources/simple/paragraph/paragraph10.test b/xwiki-rendering-integration-tests/src/test/resources/simple/paragraph/paragraph10.test new file mode 100644 index 0000000000..b497e044b6 --- /dev/null +++ b/xwiki-rendering-integration-tests/src/test/resources/simple/paragraph/paragraph10.test @@ -0,0 +1,31 @@ +.#--------------------------------------------------------------------------------------------------------- +.# An empty format (parameters only) at the very end of a paragraph is currently dropped. Unlike a format right +.# after a still-pending macro or verbatim element (see XRENDERING-766), plain inline content followed by a trailing +.# empty parameter format does not preserve that format. +.#--------------------------------------------------------------------------------------------------------- +.input|xwiki/2.0 +.#--------------------------------------------------------------------------------------------------------- +Paragraph1(% id="test" %) + +Paragraph2 + +Paragraph3(% id="test" %)(%%) + +Paragraph4 +.#--------------------------------------------------------------------------------------------------------- +.expect|event/1.0 +.#--------------------------------------------------------------------------------------------------------- +beginDocument +beginParagraph +onWord [Paragraph1] +endParagraph +beginParagraph +onWord [Paragraph2] +endParagraph +beginParagraph +onWord [Paragraph3] +endParagraph +beginParagraph +onWord [Paragraph4] +endParagraph +endDocument diff --git a/xwiki-rendering-integration-tests/src/test/resources/simple/verbatim/verbatim9.test b/xwiki-rendering-integration-tests/src/test/resources/simple/verbatim/verbatim9.test new file mode 100644 index 0000000000..724ba4c6bb --- /dev/null +++ b/xwiki-rendering-integration-tests/src/test/resources/simple/verbatim/verbatim9.test @@ -0,0 +1,67 @@ +.#--------------------------------------------------------------------------------------------------------- +.# A non-empty format after verbatim text must keep the verbatim inline without wrapping it in the format +.# (XRENDERING-766). A format before verbatim still wraps it. An *empty* format right after verbatim carries +.# no content: the format is dropped and the verbatim is emitted as a standalone block, exactly as if the +.# format were not there. The dropped format must not leak into the following block either - the fourth case +.# is followed by "Padding", which must stay unformatted. The xwiki/2.0 input therefore does not round-trip +.# (the empty format disappears), so input and output are specified separately. +.#--------------------------------------------------------------------------------------------------------- +.input|xwiki/2.0 +.#--------------------------------------------------------------------------------------------------------- +{{{verbatim}}}(% id="test" %)hi + +(% id="test" %){{{verbatim}}}hi + +(% id="test" %){{{verbatim}}} + +{{{verbatim}}}(% id="test" %) + +Padding + +{{{verbatim1}}}(% id="test" %){{{verbatim2}}} +.#--------------------------------------------------------------------------------------------------------- +.expect|event/1.0 +.#--------------------------------------------------------------------------------------------------------- +beginDocument +beginParagraph +onVerbatim [verbatim] [true] +beginFormat [NONE] [[id]=[test]] +onWord [hi] +endFormat [NONE] [[id]=[test]] +endParagraph +beginParagraph +beginFormat [NONE] [[id]=[test]] +onVerbatim [verbatim] [true] +onWord [hi] +endFormat [NONE] [[id]=[test]] +endParagraph +beginParagraph +beginFormat [NONE] [[id]=[test]] +onVerbatim [verbatim] [true] +endFormat [NONE] [[id]=[test]] +endParagraph +onVerbatim [verbatim] [false] +beginParagraph +onWord [Padding] +endParagraph +beginParagraph +onVerbatim [verbatim1] [true] +beginFormat [NONE] [[id]=[test]] +onVerbatim [verbatim2] [true] +endFormat [NONE] [[id]=[test]] +endParagraph +endDocument +.#--------------------------------------------------------------------------------------------------------- +.expect|xwiki/2.0 +.#--------------------------------------------------------------------------------------------------------- +{{{verbatim}}}(% id="test" %)hi + +(% id="test" %){{{verbatim}}}hi + +(% id="test" %){{{verbatim}}} + +{{{verbatim}}} + +Padding + +{{{verbatim1}}}(% id="test" %){{{verbatim2}}} diff --git a/xwiki-rendering-syntaxes/xwiki-rendering-syntax-annotatedxhtml/src/test/resources/annotatedxhtml10/specific/macro/macro1.test b/xwiki-rendering-syntaxes/xwiki-rendering-syntax-annotatedxhtml/src/test/resources/annotatedxhtml10/specific/macro/macro1.test new file mode 100644 index 0000000000..8bcba24374 --- /dev/null +++ b/xwiki-rendering-syntaxes/xwiki-rendering-syntax-annotatedxhtml/src/test/resources/annotatedxhtml10/specific/macro/macro1.test @@ -0,0 +1,12 @@ +.#----------------------------------------------------- +.input|xhtml/1.0 +.# An inline macro followed by the CKEditor "xwiki-macro-inline-enforcer" span (a parameter format holding a +.# non-breaking space, used to keep the macro inline) must round-trip with the macro *before* the span and never +.# wrapped inside it. If the macro were wrapped, CKEditor's removal of the enforcer span would delete the macro too +.# (XWIKI-21973 / XRENDERING-766). +.#----------------------------------------------------- +
+.#----------------------------------------------------- +.expect|annotatedxhtml/1.0 +.#----------------------------------------------------- +
diff --git a/xwiki-rendering-syntaxes/xwiki-rendering-syntax-xhtml/src/test/resources/xhtml10/specific/macro/macro12.test b/xwiki-rendering-syntaxes/xwiki-rendering-syntax-xhtml/src/test/resources/xhtml10/specific/macro/macro12.test new file mode 100644 index 0000000000..79dd4fb15b --- /dev/null +++ b/xwiki-rendering-syntaxes/xwiki-rendering-syntax-xhtml/src/test/resources/xhtml10/specific/macro/macro12.test @@ -0,0 +1,19 @@ +.#----------------------------------------------------- +.input|xhtml/1.0 +.# An inline macro followed by CKEditor's "xwiki-macro-inline-enforcer" span must be parsed as an inline macro that +.# is *not* wrapped in the enforcer format: the onMacroInline event comes before beginFormat, not inside it. If the +.# macro were wrapped, CKEditor's removal of the enforcer span would delete the macro too (XWIKI-21973 / +.# XRENDERING-766). The enforcer span carries a non-breaking space, which is why it keeps the macro inline. +.#----------------------------------------------------- +
+.#----------------------------------------------------- +.expect|event/1.0 +.#----------------------------------------------------- +beginDocument +beginParagraph +onMacroInline [mention] [reference=XWiki.Admin|style=FULL_NAME|anchor=XWiki-Admin-7qcxlk] +beginFormat [NONE] [[id]=[xwiki-macro-inline-enforcer]] +onSpace +endFormat [NONE] [[id]=[xwiki-macro-inline-enforcer]] +endParagraph +endDocument diff --git a/xwiki-rendering-wikimodel/src/main/java/org/xwiki/rendering/wikimodel/impl/InternalWikiScannerContext.java b/xwiki-rendering-wikimodel/src/main/java/org/xwiki/rendering/wikimodel/impl/InternalWikiScannerContext.java index 053ab6ef9f..af761588a6 100644 --- a/xwiki-rendering-wikimodel/src/main/java/org/xwiki/rendering/wikimodel/impl/InternalWikiScannerContext.java +++ b/xwiki-rendering-wikimodel/src/main/java/org/xwiki/rendering/wikimodel/impl/InternalWikiScannerContext.java @@ -599,9 +599,12 @@ protected void checkStyleOpened() fMacroName = macroName; fVerbatimContent = verbatimContent; } - openFormat(); + // First check if we encountered a verbatim or macro that we can now process as inline. + // Then open whatever format we encountered. If the format was before the verbatim or macro, we would have + // immediately recognized both as inline and processed them already. checkVerbatim(true); checkMacro(true); + openFormat(); } private void checkTableCell() @@ -627,6 +630,17 @@ private void checkVerbatim(boolean inline) public void closeBlock() { + // A format that trails a still-pending macro or verbatim element has no content: the element is emitted as + // a standalone block below, and the empty format is dropped here so it does not leak into the next block. + // The macro/verbatim condition is required: it excludes the checkStyleOpened() -> beginParagraph() -> + // closeBlock() re-entrant call (which temporarily clears these fields), where the pending format must be + // preserved so that it can wrap the following inline content. + if ((fMacroName != null || fVerbatimContent != null) && isNoBlockElements() + && !WikiFormat.EMPTY.equals(fNewFormat)) + { + fNewFormat = WikiFormat.EMPTY; + } + checkVerbatim(false); checkMacro(false); diff --git a/xwiki-rendering-wikimodel/src/test/java/org/xwiki/rendering/wikimodel/test/XWiki20ParserTest.java b/xwiki-rendering-wikimodel/src/test/java/org/xwiki/rendering/wikimodel/test/XWiki20ParserTest.java index 5566b564e7..387e94efaa 100644 --- a/xwiki-rendering-wikimodel/src/test/java/org/xwiki/rendering/wikimodel/test/XWiki20ParserTest.java +++ b/xwiki-rendering-wikimodel/src/test/java/org/xwiki/rendering/wikimodel/test/XWiki20ParserTest.java @@ -703,6 +703,44 @@ void testMacro() throws WikiParserException ""); } + /** + * A macro at the start of a line followed by a format must be inline, and the format must not wrap the macro, + * see XRENDERING-766. + */ + @Test + void testMacroFollowedByFormat() throws WikiParserException + { + // The format must not wrap the macro, only the content after it. + test("{{macro/}}(% id='test' %)hi", + "
" + + "hi
"); + + // A format before the macro wraps the macro, though. + test("(% id='test' %){{macro/}}", + "" + + "
"); + + // A trailing format with no content is dropped, and the macro becomes a standalone block (as if the + // empty format were not there). + test("{{macro/}}(% id='test' %)", + ""); + + // The dropped empty format must not leak into the following paragraph. + test("{{macro/}}(% id='test' %)\n\nPadding", + "\n" + + "Padding
"); + + // Only the second macro must be wrapped in the format. + test("{{macro/}}(% id='test' %){{macro/}}", + "" + + "
"); + + // The same applies to verbatim content: an empty trailing format is dropped and the verbatim becomes + // a standalone block. + test("{{{verbatim}}}(% id='test' %)", + "verbatim"); + } + @Test void testMacroParameterEscaping() throws WikiParserException {