diff --git a/jspwiki-main/src/main/java/org/apache/wiki/diff/ContextualDiffProvider.java b/jspwiki-main/src/main/java/org/apache/wiki/diff/ContextualDiffProvider.java
deleted file mode 100644
index 32d96bce18..0000000000
--- a/jspwiki-main/src/main/java/org/apache/wiki/diff/ContextualDiffProvider.java
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-
-package org.apache.wiki.diff;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.wiki.api.core.Context;
-import org.apache.wiki.api.core.Engine;
-import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
-import org.apache.wiki.util.TextUtil;
-import org.suigeneris.jrcs.diff.Diff;
-import org.suigeneris.jrcs.diff.DifferentiationFailedException;
-import org.suigeneris.jrcs.diff.Revision;
-import org.suigeneris.jrcs.diff.RevisionVisitor;
-import org.suigeneris.jrcs.diff.delta.AddDelta;
-import org.suigeneris.jrcs.diff.delta.ChangeDelta;
-import org.suigeneris.jrcs.diff.delta.Chunk;
-import org.suigeneris.jrcs.diff.delta.DeleteDelta;
-import org.suigeneris.jrcs.diff.delta.Delta;
-import org.suigeneris.jrcs.diff.myers.MyersDiff;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Properties;
-import java.util.StringTokenizer;
-import java.util.stream.Collectors;
-
-
-/**
- * A seriously better diff provider, which highlights changes word-by-word using CSS.
- *
- * Suggested by John Volkar.
- */
-public class ContextualDiffProvider implements DiffProvider {
-
- private static final Logger LOG = LogManager.getLogger( ContextualDiffProvider.class );
-
- /**
- * A jspwiki.properties value to define how many characters are shown around the change context.
- * The current value is {@value}.
- */
- public static final String PROP_UNCHANGED_CONTEXT_LIMIT = "jspwiki.contextualDiffProvider.unchangedContextLimit";
-
- //TODO all of these publics can become jspwiki.properties entries...
- //TODO span title= can be used to get hover info...
-
- public boolean m_emitChangeNextPreviousHyperlinks = true;
-
- //Don't use spans here the deletion and insertions are nested in this...
- public static String CHANGE_START_HTML = ""; //This could be a image '>' for a start marker
- public static String CHANGE_END_HTML = ""; //and an image for an end '<' marker
- public static String DIFF_START = "";
- public static String DIFF_END = "
";
-
- // Unfortunately we need to do dumb HTML here for RSS feeds.
-
- public static String INSERTION_START_HTML = "";
- public static String INSERTION_END_HTML = "";
- public static String DELETION_START_HTML = "";
- public static String DELETION_END_HTML = "";
- private static final String ANCHOR_PRE_INDEX = "";
- private static final String BACK_PRE_INDEX = "<<";
- private static final String FORWARD_PRE_INDEX = ">>";
- public static String ELIDED_HEAD_INDICATOR_HTML = "
...";
- public static String ELIDED_TAIL_INDICATOR_HTML = "...
";
- public static String LINE_BREAK_HTML = "
";
- public static String ALTERNATING_SPACE_HTML = " ";
-
- // This one, I will make property file based...
- private static final int LIMIT_MAX_VALUE = (Integer.MAX_VALUE /2) - 1;
- private int m_unchangedContextLimit = LIMIT_MAX_VALUE;
-
-
- /**
- * Constructs this provider.
- */
- public ContextualDiffProvider()
- {}
-
- /**
- * @see org.apache.wiki.api.providers.WikiProvider#getProviderInfo()
- *
- * {@inheritDoc}
- */
- @Override
- public String getProviderInfo()
- {
- return "ContextualDiffProvider";
- }
-
- /**
- * @see org.apache.wiki.api.providers.WikiProvider#initialize(org.apache.wiki.api.core.Engine, java.util.Properties)
- *
- * {@inheritDoc}
- */
- @Override
- public void initialize( final Engine engine, final Properties properties) throws NoRequiredPropertyException, IOException {
- final String configuredLimit = properties.getProperty( PROP_UNCHANGED_CONTEXT_LIMIT, Integer.toString( LIMIT_MAX_VALUE ) );
- int limit = LIMIT_MAX_VALUE;
- try {
- limit = Integer.parseInt( configuredLimit );
- } catch( final NumberFormatException e ) {
- LOG.warn("Failed to parseInt " + PROP_UNCHANGED_CONTEXT_LIMIT + "=" + configuredLimit + " Will use a huge number as limit.", e );
- }
- m_unchangedContextLimit = limit;
- }
-
-
-
- /**
- * Do a colored diff of the two regions. This. is. serious. fun. ;-)
- *
- * @see org.apache.wiki.diff.DiffProvider#makeDiffHtml(Context, String, String)
- *
- * {@inheritDoc}
- */
- @Override
- public synchronized String makeDiffHtml( final Context ctx, final String wikiOld, final String wikiNew ) {
- //
- // Sequencing handles lineterminator to
and every-other consequtive space to a
- //
- final String[] alpha = sequence( TextUtil.replaceEntities( wikiOld ) );
- final String[] beta = sequence( TextUtil.replaceEntities( wikiNew ) );
-
- final Revision rev;
- try {
- rev = Diff.diff( alpha, beta, new MyersDiff() );
- } catch( final DifferentiationFailedException dfe ) {
- LOG.error( "Diff generation failed", dfe );
- return "Error while creating version diff.";
- }
-
- final int revSize = rev.size();
- final StringBuffer sb = new StringBuffer();
-
- sb.append( DIFF_START );
-
- //
- // The MyersDiff is a bit dumb by converting a single line multi-word diff into a series
- // of Changes. The ChangeMerger pulls them together again...
- //
- final ChangeMerger cm = new ChangeMerger( sb, alpha, revSize );
- rev.accept( cm );
- cm.shutdown();
- sb.append( DIFF_END );
- return sb.toString();
- }
-
- /**
- * Take the string and create an array from it, split it first on newlines, making
- * sure to preserve the newlines in the elements, split each resulting element on
- * spaces, preserving the spaces.
- *
- * All this preseving of newlines and spaces is so the wikitext when diffed will have fidelity
- * to it's original form. As a side affect we see edits of purely whilespace.
- */
- private String[] sequence( final String wikiText ) {
- final String[] linesArray = Diff.stringToArray( wikiText );
- final List< String > list = new ArrayList<>();
- for( final String line : linesArray ) {
-
- String lastToken = null;
- String token;
- // StringTokenizer might be discouraged but it still is perfect here...
- for( final StringTokenizer st = new StringTokenizer( line, " ", true ); st.hasMoreTokens(); ) {
- token = st.nextToken();
-
- if( " ".equals( lastToken ) && " ".equals( token ) ) {
- token = ALTERNATING_SPACE_HTML;
- }
-
- list.add( token );
- lastToken = token;
- }
-
- list.add( LINE_BREAK_HTML ); // Line Break
- }
-
- return list.toArray( new String[ 0 ] );
- }
-
- /**
- * This helper class does the housekeeping for merging
- * our various changes down and also makes sure that the
- * whole change process is threadsafe by encapsulating
- * all necessary variables.
- */
- private final class ChangeMerger implements RevisionVisitor {
- private final StringBuffer m_sb;
-
- /** Keeping score of the original lines to process */
- private final int m_max;
-
- private int m_index;
-
- /** Index of the next element to be copied into the output. */
- private int m_firstElem;
-
- /** Link Anchor counter */
- private int m_count = 1;
-
- /** State Machine Mode */
- private int m_mode = -1; /* -1: Unset, 0: Add, 1: Del, 2: Change mode */
-
- /** Buffer to coalesce the changes together */
- private StringBuffer m_origBuf;
-
- private StringBuffer m_newBuf;
-
- /** Reference to the source string array */
- private final String[] m_origStrings;
-
- private ChangeMerger( final StringBuffer sb, final String[] origStrings, final int max ) {
- m_sb = sb;
- m_origStrings = origStrings != null ? origStrings.clone() : null;
- m_max = max;
-
- m_origBuf = new StringBuffer();
- m_newBuf = new StringBuffer();
- }
-
- private void updateState( final Delta delta ) {
- m_index++;
- final Chunk orig = delta.getOriginal();
- if( orig.first() > m_firstElem ) {
- // We "skip" some lines in the output.
- // So flush out the last Change, if one exists.
- flushChanges();
-
- // Allow us to "skip" large swaths of unchanged text, show a "limited" amound of
- // unchanged context so the changes are shown in
- if( ( orig.first() - m_firstElem ) > 2 * m_unchangedContextLimit ) {
- if (m_firstElem > 0) {
- final int endIndex = Math.min( m_firstElem + m_unchangedContextLimit, m_origStrings.length -1 );
-
- m_sb.append(Arrays.stream(m_origStrings, m_firstElem, endIndex).collect(Collectors.joining("", "", ELIDED_TAIL_INDICATOR_HTML)));
-
- }
-
- m_sb.append( ELIDED_HEAD_INDICATOR_HTML );
-
- final int startIndex = Math.max(orig.first() - m_unchangedContextLimit, 0);
- m_sb.append(Arrays.stream(m_origStrings, startIndex, orig.first()).collect(Collectors.joining()));
-
- } else {
- // No need to skip anything, just output the whole range...
- m_sb.append(Arrays.stream(m_origStrings, m_firstElem, orig.first()).collect(Collectors.joining()));
- }
- }
- m_firstElem = orig.last() + 1;
- }
-
- @Override
- public void visit( final Revision rev ) {
- // GNDN (Goes nowhere, does nothing)
- }
-
- @Override
- public void visit( final AddDelta delta ) {
- updateState( delta );
-
- // We have run Deletes up to now. Flush them out.
- if( m_mode == 1 ) {
- flushChanges();
- m_mode = -1;
- }
- // We are in "neutral mode". Start a new Change
- if( m_mode == -1 ) {
- m_mode = 0;
- }
-
- // We are in "add mode".
- if( m_mode == 0 || m_mode == 2 ) {
- addNew( delta.getRevised() );
- m_mode = 1;
- }
- }
-
- @Override
- public void visit( final ChangeDelta delta ) {
- updateState( delta );
-
- // We are in "neutral mode". A Change might be merged with an add or delete.
- if( m_mode == -1 ) {
- m_mode = 2;
- }
-
- // Add the Changes to the buffers.
- addOrig( delta.getOriginal() );
- addNew( delta.getRevised() );
- }
-
- @Override
- public void visit( final DeleteDelta delta ) {
- updateState( delta );
-
- // We have run Adds up to now. Flush them out.
- if( m_mode == 0 ) {
- flushChanges();
- m_mode = -1;
- }
- // We are in "neutral mode". Start a new Change
- if( m_mode == -1 ) {
- m_mode = 1;
- }
-
- // We are in "delete mode".
- if( m_mode == 1 || m_mode == 2 ) {
- addOrig( delta.getOriginal() );
- m_mode = 1;
- }
- }
-
- public void shutdown() {
- m_index = m_max + 1; // Make sure that no hyperlink gets created
- flushChanges();
-
- if( m_firstElem < m_origStrings.length ) {
- // If there's more than the limit of the orginal left just emit limit and elided...
- if( ( m_origStrings.length - m_firstElem ) > m_unchangedContextLimit ) {
- final int endIndex = Math.min( m_firstElem + m_unchangedContextLimit, m_origStrings.length -1 );
- m_sb.append(Arrays.stream(m_origStrings, m_firstElem, endIndex).collect(Collectors.joining("", "", ELIDED_TAIL_INDICATOR_HTML)));
-
- } else {
- // emit entire tail of original...
- m_sb.append(Arrays.stream(m_origStrings, m_firstElem, m_origStrings.length).collect(Collectors.joining()));
- }
- }
- }
-
- private void addOrig( final Chunk chunk ) {
- if( chunk != null ) {
- chunk.toString( m_origBuf );
- }
- }
-
- private void addNew( final Chunk chunk ) {
- if( chunk != null ) {
- chunk.toString( m_newBuf );
- }
- }
-
- private void flushChanges() {
- if( m_newBuf.length() + m_origBuf.length() > 0 ) {
- // This is the span element which encapsulates anchor and the change itself
- m_sb.append( CHANGE_START_HTML );
-
- // Do we want to have a "back link"?
- if( m_emitChangeNextPreviousHyperlinks && m_count > 1 ) {
- m_sb.append( BACK_PRE_INDEX );
- m_sb.append( m_count - 1 );
- m_sb.append( BACK_POST_INDEX );
- }
-
- // An anchor for the change.
- if (m_emitChangeNextPreviousHyperlinks) {
- m_sb.append( ANCHOR_PRE_INDEX );
- m_sb.append( m_count++ );
- m_sb.append( ANCHOR_POST_INDEX );
- }
-
- // ... has been added
- if( m_newBuf.length() > 0 ) {
- m_sb.append( INSERTION_START_HTML );
- m_sb.append( m_newBuf );
- m_sb.append( INSERTION_END_HTML );
- }
-
- // .. has been removed
- if( m_origBuf.length() > 0 ) {
- m_sb.append( DELETION_START_HTML );
- m_sb.append( m_origBuf );
- m_sb.append( DELETION_END_HTML );
- }
-
- // Do we want a "forward" link?
- if( m_emitChangeNextPreviousHyperlinks && (m_index < m_max) ) {
- m_sb.append( FORWARD_PRE_INDEX );
- m_sb.append( m_count ); // Has already been incremented.
- m_sb.append( FORWARD_POST_INDEX );
- }
-
- m_sb.append( CHANGE_END_HTML );
-
- // Nuke the buffers.
- m_origBuf = new StringBuffer();
- m_newBuf = new StringBuffer();
- }
-
- // After a flush, everything is reset.
- m_mode = -1;
- }
- }
-
-}
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/diff/DefaultDifferenceManager.java b/jspwiki-main/src/main/java/org/apache/wiki/diff/DefaultDifferenceManager.java
index 0c15bb683c..7ad05284ef 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/diff/DefaultDifferenceManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/diff/DefaultDifferenceManager.java
@@ -55,7 +55,7 @@ public DefaultDifferenceManager( final Engine engine, final Properties props ) {
}
private void loadProvider( final Properties props ) {
- final String providerClassName = props.getProperty( PROP_DIFF_PROVIDER, TraditionalDiffProvider.class.getName() );
+ final String providerClassName = props.getProperty( PROP_DIFF_PROVIDER, SvnStyleDiffProvider.class.getName() );
try {
m_provider = ClassUtil.buildInstance( "org.apache.wiki.diff", providerClassName );
} catch( final ReflectiveOperationException e ) {
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/diff/SvnStyleDiffProvider.java b/jspwiki-main/src/main/java/org/apache/wiki/diff/SvnStyleDiffProvider.java
new file mode 100644
index 0000000000..5804b887fc
--- /dev/null
+++ b/jspwiki-main/src/main/java/org/apache/wiki/diff/SvnStyleDiffProvider.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2025 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wiki.diff;
+
+import com.github.difflib.DiffUtils;
+import com.github.difflib.patch.AbstractDelta;
+import com.github.difflib.patch.Patch;
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+import org.apache.wiki.api.core.Context;
+import org.apache.wiki.api.core.Engine;
+import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
+
+/**
+ * SVN/Git style diff provider. Uses the DiffLib, ASF 2.0 licensed.
+ *
+ * @since 3.0.0
+ */
+public class SvnStyleDiffProvider implements DiffProvider {
+
+ public static final String CSS_DIFF_ADDED = "| ";
+ public static final String CSS_DIFF_REMOVED = " |
| ";
+ public static final String CSS_DIFF_UNCHANGED = " |
| ";
+ public static final String CSS_DIFF_CLOSE = " |
\n";
+ public static final String CELL_CHANGE = "";
+
+ @Override
+ public String makeDiffHtml(Context context, String originalText, String modifiedText) {
+
+ List original = originalText.lines().toList();
+ List modified = modifiedText.lines().toList();
+ StringBuilder ret = new StringBuilder();
+ ret.append("\n");
+
+ Patch patch = DiffUtils.diff(original, modified);
+ int lineNumber = 1;
+ int currentOriginalLine = 0;
+ int currentModifiedLine = 0;
+
+ for (AbstractDelta delta : patch.getDeltas()) {
+ int originalPosition = delta.getSource().getPosition();
+ int modifiedPosition = delta.getTarget().getPosition();
+
+ // Output unchanged lines before the delta
+ while (currentOriginalLine < originalPosition && currentModifiedLine < modifiedPosition) {
+ ret.append(CSS_DIFF_UNCHANGED);
+ ret.append(lineNumber).append(CELL_CHANGE);
+ ret.append(original.get(currentOriginalLine));
+ ret.append(CSS_DIFF_CLOSE);
+ //System.out.println(" " + lineNumber + " " + original.get(currentOriginalLine));
+ lineNumber++;
+ currentOriginalLine++;
+ currentModifiedLine++;
+ }
+
+ List originalLines = delta.getSource().getLines();
+ List revisedLines = delta.getTarget().getLines();
+
+ for (String line : originalLines) {
+ ret.append(CSS_DIFF_REMOVED);
+ ret.append(lineNumber).append(CELL_CHANGE);
+ ret.append(line);
+ ret.append(CSS_DIFF_CLOSE);
+ //System.out.println("- " + lineNumber + " " + line);
+ lineNumber++;
+ currentOriginalLine++;
+ }
+
+ for (String line : revisedLines) {
+ ret.append(CSS_DIFF_ADDED);
+ ret.append(lineNumber).append(CELL_CHANGE);
+ ret.append(line);
+ ret.append(CSS_DIFF_CLOSE);
+ //System.out.println("+ " + lineNumber + " " + line);
+ lineNumber++;
+ currentModifiedLine++;
+ }
+ }
+
+ // Output any remaining unchanged lines at the end
+ while (currentOriginalLine < original.size() && currentModifiedLine < modified.size()) {
+ ret.append(CSS_DIFF_UNCHANGED);
+ ret.append(lineNumber).append(CELL_CHANGE);
+ ret.append(original.get(currentOriginalLine));
+ ret.append(CSS_DIFF_CLOSE);
+
+ //System.out.println(" " + lineNumber + " " + original.get(currentOriginalLine));
+ lineNumber++;
+ currentOriginalLine++;
+ currentModifiedLine++;
+ }
+ ret.append(" \n");
+ return ret.toString();
+ }
+
+ @Override
+ public void initialize(Engine engine, Properties properties) throws NoRequiredPropertyException, IOException {
+ }
+
+ @Override
+ public String getProviderInfo() {
+ return "SvnStyleDiffProvider";
+ }
+
+}
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/diff/TraditionalDiffProvider.java b/jspwiki-main/src/main/java/org/apache/wiki/diff/TraditionalDiffProvider.java
deleted file mode 100644
index cbe13992ed..0000000000
--- a/jspwiki-main/src/main/java/org/apache/wiki/diff/TraditionalDiffProvider.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-
-package org.apache.wiki.diff;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.wiki.api.core.Context;
-import org.apache.wiki.api.core.Engine;
-import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
-import org.apache.wiki.i18n.InternationalizationManager;
-import org.apache.wiki.preferences.Preferences;
-import org.apache.wiki.util.TextUtil;
-import org.suigeneris.jrcs.diff.Diff;
-import org.suigeneris.jrcs.diff.DifferentiationFailedException;
-import org.suigeneris.jrcs.diff.Revision;
-import org.suigeneris.jrcs.diff.RevisionVisitor;
-import org.suigeneris.jrcs.diff.delta.AddDelta;
-import org.suigeneris.jrcs.diff.delta.ChangeDelta;
-import org.suigeneris.jrcs.diff.delta.Chunk;
-import org.suigeneris.jrcs.diff.delta.DeleteDelta;
-import org.suigeneris.jrcs.diff.myers.MyersDiff;
-
-import java.io.IOException;
-import java.text.ChoiceFormat;
-import java.text.Format;
-import java.text.MessageFormat;
-import java.text.NumberFormat;
-import java.util.Properties;
-import java.util.ResourceBundle;
-
-
-/**
- * This is the JSPWiki 'traditional' diff. It uses an internal diff engine.
- */
-public class TraditionalDiffProvider implements DiffProvider {
-
- private static final Logger LOG = LogManager.getLogger( TraditionalDiffProvider.class );
- private static final String CSS_DIFF_ADDED = "| ";
- private static final String CSS_DIFF_REMOVED = " | | ";
- private static final String CSS_DIFF_UNCHANGED = " | | ";
- private static final String CSS_DIFF_CLOSE = " | " + Diff.NL;
-
- /**
- * Constructs the provider.
- */
- public TraditionalDiffProvider() {
- }
-
- /**
- * {@inheritDoc}
- * @see org.apache.wiki.api.providers.WikiProvider#getProviderInfo()
- */
- @Override
- public String getProviderInfo()
- {
- return "TraditionalDiffProvider";
- }
-
- /**
- * {@inheritDoc}
- * @see org.apache.wiki.api.providers.WikiProvider#initialize(org.apache.wiki.api.core.Engine, java.util.Properties)
- */
- @Override
- public void initialize( final Engine engine, final Properties properties ) throws NoRequiredPropertyException, IOException {
- }
-
- /**
- * Makes a diff using the BMSI utility package. We use our own diff printer,
- * which makes things easier.
- *
- * @param ctx The WikiContext in which the diff should be made.
- * @param p1 The first string
- * @param p2 The second string.
- *
- * @return Full HTML diff.
- */
- @Override
- public String makeDiffHtml( final Context ctx, final String p1, final String p2 ) {
- final String diffResult;
-
- try {
- final String[] first = Diff.stringToArray(TextUtil.replaceEntities(p1));
- final String[] second = Diff.stringToArray(TextUtil.replaceEntities(p2));
- final Revision rev = Diff.diff(first, second, new MyersDiff());
-
- if( rev == null || rev.size() == 0 ) {
- // No difference
- return "";
- }
-
- final StringBuffer ret = new StringBuffer(rev.size() * 20); // Guessing how big it will become...
-
- ret.append( "\n" );
- rev.accept( new RevisionPrint( ctx, ret ) );
- ret.append( " \n" );
-
- return ret.toString();
- } catch( final DifferentiationFailedException e ) {
- diffResult = "makeDiff failed with DifferentiationFailedException";
- LOG.error( diffResult, e );
- }
-
- return diffResult;
- }
-
-
- private static final class RevisionPrint implements RevisionVisitor {
-
- private final StringBuffer m_result;
- private final Context m_context;
- private final ResourceBundle m_rb;
-
- private RevisionPrint( final Context ctx, final StringBuffer sb ) {
- m_result = sb;
- m_context = ctx;
- m_rb = Preferences.getBundle( ctx, InternationalizationManager.CORE_BUNDLE );
- }
-
- @Override
- public void visit( final Revision rev ) {
- // GNDN (Goes nowhere, does nothing)
- }
-
- @Override
- public void visit( final AddDelta delta ) {
- final Chunk changed = delta.getRevised();
- print( changed, m_rb.getString( "diff.traditional.added" ) );
- changed.toString( m_result, CSS_DIFF_ADDED, CSS_DIFF_CLOSE );
- }
-
- @Override
- public void visit( final ChangeDelta delta ) {
- final Chunk changed = delta.getOriginal();
- print(changed, m_rb.getString( "diff.traditional.changed" ) );
- changed.toString( m_result, CSS_DIFF_REMOVED, CSS_DIFF_CLOSE );
- delta.getRevised().toString( m_result, CSS_DIFF_ADDED, CSS_DIFF_CLOSE );
- }
-
- @Override
- public void visit( final DeleteDelta delta ) {
- final Chunk changed = delta.getOriginal();
- print( changed, m_rb.getString( "diff.traditional.removed" ) );
- changed.toString( m_result, CSS_DIFF_REMOVED, CSS_DIFF_CLOSE );
- }
-
- private void print( final Chunk changed, final String type ) {
- m_result.append( CSS_DIFF_UNCHANGED );
-
- final String[] choiceString = {
- m_rb.getString("diff.traditional.oneline"),
- m_rb.getString("diff.traditional.lines")
- };
- final double[] choiceLimits = { 1, 2 };
-
- final MessageFormat fmt = new MessageFormat("");
- fmt.setLocale( Preferences.getLocale(m_context) );
- final ChoiceFormat cfmt = new ChoiceFormat( choiceLimits, choiceString );
- fmt.applyPattern( type );
- final Format[] formats = { NumberFormat.getInstance(), cfmt, NumberFormat.getInstance() };
- fmt.setFormats( formats );
-
- final Object[] params = { changed.first() + 1,
- changed.size(),
- changed.size() };
- m_result.append( fmt.format(params) );
- m_result.append( CSS_DIFF_CLOSE );
- }
- }
-
-}
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/filters/SpamFilter.java b/jspwiki-main/src/main/java/org/apache/wiki/filters/SpamFilter.java
index 08d11b4a96..78ff1f1273 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/filters/SpamFilter.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/filters/SpamFilter.java
@@ -18,6 +18,9 @@ Licensed to the Apache Software Foundation (ASF) under one
*/
package org.apache.wiki.filters;
+import com.github.difflib.DiffUtils;
+import com.github.difflib.patch.AbstractDelta;
+import com.github.difflib.patch.Patch;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.logging.log4j.LogManager;
@@ -46,14 +49,6 @@ Licensed to the Apache Software Foundation (ASF) under one
import org.apache.wiki.util.FileUtil;
import org.apache.wiki.util.HttpUtil;
import org.apache.wiki.util.TextUtil;
-import org.suigeneris.jrcs.diff.Diff;
-import org.suigeneris.jrcs.diff.DifferentiationFailedException;
-import org.suigeneris.jrcs.diff.Revision;
-import org.suigeneris.jrcs.diff.delta.AddDelta;
-import org.suigeneris.jrcs.diff.delta.ChangeDelta;
-import org.suigeneris.jrcs.diff.delta.DeleteDelta;
-import org.suigeneris.jrcs.diff.delta.Delta;
-import org.suigeneris.jrcs.diff.myers.MyersDiff;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -808,31 +803,48 @@ private static Change getChange( final Context context, final String newText ) {
try {
final String oldText = engine.getManager( PageManager.class ).getPureText( page.getName(), WikiProvider.LATEST_VERSION );
- final String[] first = Diff.stringToArray( oldText );
- final String[] second = Diff.stringToArray( newText );
- final Revision rev = Diff.diff( first, second, new MyersDiff() );
-
- if( rev == null || rev.size() == 0 ) {
+ Patch patch = DiffUtils.diffInline(oldText, newText);
+
+
+ if( patch == null ) {
return ch;
}
-
- for( int i = 0; i < rev.size(); i++ ) {
- final Delta d = rev.getDelta( i );
+ int lineNumber = 1;
+ int currentOriginalLine = 0;
+ int currentModifiedLine = 0;
+
+ for (AbstractDelta delta : patch.getDeltas()) {
+ int originalPosition = delta.getSource().getPosition();
+ int modifiedPosition = delta.getTarget().getPosition();
+
+ // Output unchanged lines before the delta
+ while (currentOriginalLine < originalPosition && currentModifiedLine < modifiedPosition) {
+ lineNumber++;
+ currentOriginalLine++;
+ currentModifiedLine++;
+ }
- if( d instanceof AddDelta ) {
- d.getRevised().toString( change, "", "\r\n" );
- ch.m_adds++;
-
- } else if( d instanceof ChangeDelta ) {
- d.getRevised().toString( change, "", "\r\n" );
- ch.m_adds++;
-
- } else if( d instanceof DeleteDelta ) {
+ List originalLines = delta.getSource().getLines();
+ List revisedLines = delta.getTarget().getLines();
+
+ for (String line : originalLines) {
+ change.append("- " + lineNumber + ": " + line + "\r\n");
ch.m_removals++;
+ lineNumber++;
+ currentOriginalLine++;
+ }
+
+ for (String line : revisedLines) {
+ change.append("+ " + lineNumber + ": " + line + "\r\n");
+ lineNumber++;
+ currentModifiedLine++;
+ ch.m_adds++;
+
}
}
- } catch( final DifferentiationFailedException e ) {
- LOG.error( "Diff failed", e );
+
+ } catch (final Exception e) {
+ LOG.error("Diff failed", e);
}
// Don't forget to include the change note, too
diff --git a/jspwiki-main/src/main/resources/ini/jspwiki.properties b/jspwiki-main/src/main/resources/ini/jspwiki.properties
index 261b82c37b..d8869738b6 100644
--- a/jspwiki-main/src/main/resources/ini/jspwiki.properties
+++ b/jspwiki-main/src/main/resources/ini/jspwiki.properties
@@ -190,14 +190,9 @@ jspwiki.attachment.forceDownload= .html .htm .js .pdf .svg .xml
# To show differences between page versions, you can define a
# difference provider.
# The following choices are available:
-# * TraditionalDiffProvider - Uses internal (java) diff
+# * SvnStyleDiffProvider - Uses internal (java) diff
# to create a list of changes and shows it line by
# line colored. This is the default
-# * ContextualDiffProvider - Uses internal (java) diff
-# to create changes inline and shows it on a word by
-# word basis using CSS. This is much superior to the
-# traditional diff provider, however, it is still quite
-# new and not much tested. YMMV.
# * ExternalDiffProvider - uses a system diff program (which
# can be configured using "jspwiki.diffCommand") to
# create a unified (!) diff.
@@ -205,7 +200,7 @@ jspwiki.attachment.forceDownload= .html .htm .js .pdf .svg .xml
# Example for a diff command:
# jspwiki.diffCommand = /usr/bin/diff -u %s1 %s2
#
-jspwiki.diffProvider = TraditionalDiffProvider
+jspwiki.diffProvider = SvnStyleDiffProvider
#
# Page references
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/diff/ContextualDiffProviderTest.java b/jspwiki-main/src/test/java/org/apache/wiki/diff/SvnStyleDiffProviderTest.java
similarity index 51%
rename from jspwiki-main/src/test/java/org/apache/wiki/diff/ContextualDiffProviderTest.java
rename to jspwiki-main/src/test/java/org/apache/wiki/diff/SvnStyleDiffProviderTest.java
index 999b7811b2..9d1d9471d9 100644
--- a/jspwiki-main/src/test/java/org/apache/wiki/diff/ContextualDiffProviderTest.java
+++ b/jspwiki-main/src/test/java/org/apache/wiki/diff/SvnStyleDiffProviderTest.java
@@ -27,48 +27,21 @@ Licensed to the Apache Software Foundation (ASF) under one
import java.io.IOException;
import java.util.Properties;
+import org.apache.commons.lang3.StringUtils;
-public class ContextualDiffProviderTest {
+public class SvnStyleDiffProviderTest {
- /**
- * Sets up some shorthand notation for writing test cases.
- *
- * The quick |^Brown Fox^-Blue Monster-| jumped over |^the^| moon.
- *
- * Get it?
- */
- private void specializedNotation( final ContextualDiffProvider diff ) {
- diff.CHANGE_END_HTML = "|";
- diff.CHANGE_START_HTML = "|";
-
- diff.DELETION_END_HTML = "-";
- diff.DELETION_START_HTML = "-";
-
- diff.DIFF_END = "";
- diff.DIFF_START = "";
-
- diff.ELIDED_HEAD_INDICATOR_HTML = "...";
- diff.ELIDED_TAIL_INDICATOR_HTML = "...";
-
- diff.m_emitChangeNextPreviousHyperlinks = false;
-
- diff.INSERTION_END_HTML = "^";
- diff.INSERTION_START_HTML = "^";
-
- diff.LINE_BREAK_HTML = "";
- diff.ALTERNATING_SPACE_HTML = "_";
- }
@Test
public void testNoChanges() throws IOException, WikiException {
- diffTest( null, "", "", "" );
- diffTest( null, "A", "A", "A" );
- diffTest( null, "A B", "A B", "A B" );
+ diffTest( "", "",0);
+ diffTest( "A", "A",0);
+ diffTest( "A B", "A B",0);
- diffTest( null, " ", " ", " _ _ _" );
- diffTest( null, "A B C", "A B C", "A B _C" );
- diffTest( null, "A B C", "A B C", "A B _ C" );
+ diffTest( " ", " ",0);
+ diffTest( "A B C", "A B C",0);
+ diffTest( "A B C", "A B C",0);
}
@Test
@@ -79,63 +52,63 @@ public void testSimpleInsertions() throws IOException, WikiException {
// when writing tests.
// Simple inserts...
- diffTest( null, "A C", "A B C", "A |^B ^|C" );
- diffTest( null, "A D", "A B C D", "A |^B C ^|D" );
+ diffTest( "A C", "A B C", 1 );
+ diffTest( "A D", "A B C D", 1 );
// Simple inserts with spaces...
- diffTest( null, "A C", "A B C", "A |^B _^|C" );
- diffTest( null, "A C", "A B C", "A |^B _ ^|C" );
- diffTest( null, "A C", "A B C", "A |^B _ _^|C" );
+ diffTest( "A C", "A B C", 1);
+ diffTest( "A C", "A B C", 1 );
+ diffTest( "A C", "A B C", 1 );
// Just inserted spaces...
- diffTest( null, "A B", "A B", "A |^_^|B" );
- diffTest( null, "A B", "A B", "A |^_ ^|B" );
- diffTest( null, "A B", "A B", "A |^_ _^|B" );
- diffTest( null, "A B", "A B", "A |^_ _ ^|B" );
+ diffTest( "A B", "A B", 1 );
+ diffTest( "A B", "A B",1 );
+ diffTest( "A B", "A B", 1 );
+ diffTest( "A B", "A B", 1 );
}
@Test
public void testSimpleDeletions() throws IOException, WikiException {
// Simple deletes...
- diffTest( null, "A B C", "A C", "A |-B -|C" );
- diffTest( null, "A B C D", "A D", "A |-B C -|D" );
+ diffTest( "A B C", "A C", 1 );
+ diffTest( "A B C D", "A D", 1 );
// Simple deletes with spaces...
- diffTest( null, "A B C", "A C", "A |-B _-|C" );
- diffTest( null, "A B C", "A C", "A |-B _ -|C" );
+ diffTest( "A B C", "A C", 1 );
+ diffTest( "A B C", "A C", 1 );
// Just deleted spaces...
- diffTest( null, "A B", "A B", "A |-_-|B" );
- diffTest( null, "A B", "A B", "A |-_ -|B" );
- diffTest( null, "A B", "A B", "A |-_ _-|B" );
+ diffTest( "A B", "A B", 1 );
+ diffTest( "A B", "A B", 1 );
+ diffTest( "A B", "A B", 1 );
}
@Test
public void testContextLimits() throws IOException, WikiException {
// No change
- diffTest( "1", "A B C D E F G H I", "A B C D E F G H I", "A..." );
+ diffTest( "A B C D E F G H I", "A B C D E F G H I", 0 );
//TODO Hmm, should the diff provider instead return the string, "No Changes"?
// Bad property value, should default to huge context limit and return entire string.
- diffTest( "foobar", "A B C D E F G H I", "A B C D F G H I", "A B C D |-E -|F G H I" );
+ diffTest( "A B C D E F G H I", "A B C D F G H I", 1 );
// One simple deletion, limit context to 2...
- diffTest( "2", "A B C D E F G H I", "A B C D F G H I", "...D |-E -|F ..." );
+ diffTest( "A B C D E F G H I", "A B C D F G H I", 1);
// Deletion of first element, limit context to 2...
- diffTest( "2", "A B C D E", "B C D E", "|-A -|B ..." );
+ diffTest( "A B C D E", "B C D E", 1);
// Deletion of last element, limit context to 2...
- diffTest( "2", "A B C D E", "A B C D ", "...D |-E-|" );
+ diffTest( "A B C D E", "A B C D ", 1 );
// Two simple deletions, limit context to 2...
- diffTest( "2", "A B C D E F G H I J K L M N O P", "A B C E F G H I J K M N O P", "...C |-D -|E ......K |-L -|M ..." );
+ diffTest( "A B C D E F G H I J K L M N O P", "A B C E F G H I J K M N O P", 1 );
}
@Test
public void testMultiples() throws IOException, WikiException {
- diffTest( null, "A F", "A B C D E F", "A |^B C D E ^|F" );
- diffTest( null, "A B C D E F", "A F", "A |-B C D E -|F" );
+ diffTest( "A F", "A B C D E F", 1 );
+ diffTest( "A B C D E F", "A F", 1 );
}
@Test
@@ -143,25 +116,25 @@ public void testSimpleChanges() throws IOException, WikiException {
// *changes* are actually an insert and a delete in the output...
//single change
- diffTest( null, "A B C", "A b C", "A |^b^-B-| C" );
+ diffTest( "A B C", "A b C", 1 );
//non-consequtive changes...
- diffTest( null, "A B C D E", "A b C d E", "A |^b^-B-| C |^d^-D-| E" );
+ diffTest( "A B C D E", "A b C d E", 1 );
}
// FIXME: This test Assertions.fails; must be enabled again asap.
- /*
+
@Test
- public void testKnownProblemCases() throws NoRequiredPropertyException, IOException
+ public void testKnownProblemCases() throws Exception
{
//These all Assertions.fail...
//make two consequtive changes
- diffTest(null, "A B C D", "A b c D", "A |^b c^-B C-| D");
+ diffTest( "A B C D", "A b c D", 1);
//acually returns -> "A |^b^-B-| |^c^-C-| D"
//collapse adjacent elements...
- diffTest(null, "A B C D", "A BC D", "A |^BC^-B C-| D");
+ diffTest( "A B C D", "A BC D", 1);
//acually returns -> "A |^BC^-B-| |-C -|D"
@@ -169,26 +142,23 @@ public void testKnownProblemCases() throws NoRequiredPropertyException, IOExcept
//adjacent edits into one...
}
- */
+
- private void diffTest( final String contextLimit, final String oldText, final String newText, final String expectedDiff )
+ private void diffTest( final String oldText, final String newText, int expected )
throws IOException, WikiException {
- final ContextualDiffProvider diff = new ContextualDiffProvider();
-
- specializedNotation( diff );
+ final SvnStyleDiffProvider diff = new SvnStyleDiffProvider();
final Properties props = TestEngine.getTestProperties();
- if( null != contextLimit ) {
- props.put( ContextualDiffProvider.PROP_UNCHANGED_CONTEXT_LIMIT, contextLimit );
- }
-
+
diff.initialize( null, props );
final TestEngine engine = new TestEngine( props );
final Context ctx = Wiki.context().create( engine, Wiki.contents().page( engine, "Dummy" ) );
final String actualDiff = diff.makeDiffHtml( ctx, oldText, newText );
-
- Assertions.assertEquals( expectedDiff, actualDiff );
+ int actuals = StringUtils.countMatches(actualDiff, SvnStyleDiffProvider.CSS_DIFF_ADDED ) +
+ StringUtils.countMatches(actualDiff, SvnStyleDiffProvider.CSS_DIFF_REMOVED );
+
+ Assertions.assertEquals( expected*2, actuals , actualDiff);
}
}
diff --git a/pom.xml b/pom.xml
index 0dfbd1d807..4d811eb282 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,6 +68,7 @@
2.0.2
2.1.5
6.1.0
+ 4.16
0.8.14
0.4.0
2.0.0
@@ -283,6 +284,12 @@
jakarta.servlet.jsp.jstl-api
${jakarta-jstl-api.version}
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+ ${java-diff-utils-version}
+
jaxen
@@ -428,12 +435,6 @@
${jdom2.version}
-
- org.jvnet.hudson
- org.suigeneris.jrcs.diff
- ${jrcs-diff.version}
-
-
org.slf4j
slf4j-api
|