diff --git a/skel_common/readme_modders.txt b/skel_common/readme_modders.txt
index 41569d0..2cf97ff 100644
--- a/skel_common/readme_modders.txt
+++ b/skel_common/readme_modders.txt
@@ -39,6 +39,12 @@ The Append Extension
When you're not overriding something, try to use unique names, so that
it won't clobber another mod and vice versa.
+ FTL 1.6.1 introduced ... root tags wrapping XML files. If present,
+ Slipstream will remove them, append, and put them back afterward. Special
+ tags (see "Advanced XML" below) will be unaware of them. Mod files are not
+ required to include these root tags, though they can. Slipstream will remove
+ those as well.
+
General
diff --git a/src/main/java/net/vhati/modmanager/core/ModUtilities.java b/src/main/java/net/vhati/modmanager/core/ModUtilities.java
index 2cf342a..e65718f 100644
--- a/src/main/java/net/vhati/modmanager/core/ModUtilities.java
+++ b/src/main/java/net/vhati/modmanager/core/ModUtilities.java
@@ -35,7 +35,9 @@ import net.vhati.modmanager.core.SloppyXMLParser;
import ar.com.hjg.pngj.PngReader;
+import org.jdom2.Content;
import org.jdom2.Document;
+import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.JDOMParseException;
import org.jdom2.input.SAXBuilder;
@@ -85,7 +87,7 @@ public class ModUtilities {
byte[] buf = new byte[4096];
int len;
ByteArrayOutputStream tmpData = new ByteArrayOutputStream();
- while ( (len = is.read(buf)) >= 0 ) {
+ while ( (len = is.read( buf )) >= 0 ) {
tmpData.write( buf, 0, len );
}
byte[] allBytes = tmpData.toByteArray();
@@ -166,6 +168,10 @@ public class ModUtilities {
* tacked on as-is. Any xml declaration tags will be scrubbed from both
* streams, and a new one will be prepended.
*
+ * If the mainStream had <FTL> tags (introduced in FTL 1.6.1), they
+ * will be scrubbed, and new ones will be added after appending. If
+ * appendStream has those tags, they will be scrubbed.
+ *
* The two InputStreams are read, and the combined result is returned as a
* new third InputStream.
*
@@ -182,13 +188,27 @@ public class ModUtilities {
* the source of new content to append as the first argument).
*/
public static InputStream appendXMLFile( InputStream mainStream, InputStream appendStream, String encoding, String mainDescription, String appendDescription ) throws IOException {
- Pattern xmlDeclPtn = Pattern.compile( "<[?]xml [^>]*?[?]>\n*" );
+ // XML declaration, or root FTL tags.
+ Pattern comboPtn = Pattern.compile( "(<[?]xml [^>]*?[?]>\n*)|(?FTL>)" );
+ Matcher m = null;
+ boolean mainHadRootTags = false;
String mainText = decodeText( mainStream, mainDescription ).text;
- mainText = xmlDeclPtn.matcher( mainText ).replaceFirst( "" );
+ StringBuffer mainBuf = new StringBuffer( mainText.length() );
+ m = comboPtn.matcher( mainText );
+ while ( m.find() ) {
+ if ( m.group( 2 ) != null ) mainHadRootTags = true;
+ m.appendReplacement( mainBuf, "" );
+ }
+ m.appendTail( mainBuf );
String appendText = decodeText( appendStream, appendDescription ).text;
- appendText = xmlDeclPtn.matcher( appendText ).replaceFirst( "" );
+ StringBuffer appendBuf = new StringBuffer( appendText.length() );
+ m = comboPtn.matcher( appendText );
+ while ( m.find() ) {
+ m.appendReplacement( appendBuf, "" );
+ }
+ m.appendTail( appendBuf );
// Concatenate, filtering the stream to standardize newlines and encode.
//
@@ -197,10 +217,12 @@ public class ModUtilities {
Writer writer = new EOLWriter( new OutputStreamWriter( tmpData, encoder ), "\r\n" );
writer.append( "\n" );
- writer.append( mainText );
+ if ( mainHadRootTags ) writer.append( "\n" );
+ writer.append( mainBuf );
writer.append( "\n\n\n\n" );
- writer.append( appendText );
+ writer.append( appendBuf );
writer.append( "\n" );
+ if ( mainHadRootTags ) writer.append( "\n" );
writer.flush();
InputStream result = new ByteArrayInputStream( tmpData.toByteArray() );
@@ -211,6 +233,10 @@ public class ModUtilities {
/**
* Appends and modifies mainStream, using content from appendStream.
*
+ * If the mainStream had <FTL> tags (introduced in FTL 1.6.1), they
+ * will be scrubbed, and new ones will be added after appending. If
+ * appendStream has those tags, they will be scrubbed.
+ *
* The two InputStreams are read, and the combined result
* is returned as a new third InputStream.
*
@@ -227,19 +253,43 @@ public class ModUtilities {
* @see net.vhati.modmanager.core.SloppyXMLOutputProcessor
*/
public static InputStream patchXMLFile( InputStream mainStream, InputStream appendStream, String encoding, boolean globalPanic, String mainDescription, String appendDescription ) throws IOException, JDOMException {
- Pattern xmlDeclPtn = Pattern.compile( "<[?]xml [^>]*?[?]>\n*" );
+ // XML declaration, or root FTL tags.
+ Pattern comboPtn = Pattern.compile( "(<[?]xml [^>]*?[?]>\n*)|(?FTL>)" );
+ Matcher m = null;
+ boolean mainHadRootTags = false;
+ String wrapperOpenTag = "";
+ String wrapperCloseTag = "";
+ StringBuffer buf = null;
String mainText = decodeText( mainStream, mainDescription ).text;
- mainText = xmlDeclPtn.matcher( mainText ).replaceFirst( "" );
- mainText = ""+ mainText +"";
- Document mainDoc = parseStrictOrSloppyXML( mainText, mainDescription+" (wrapped)" );
+ buf = new StringBuffer( wrapperOpenTag.length() + mainText.length() + wrapperCloseTag.length() );
+ buf.append( wrapperOpenTag );
+ m = comboPtn.matcher( mainText );
+ while ( m.find() ) {
+ if ( m.group( 2 ) != null ) mainHadRootTags = true;
+ m.appendReplacement( buf, "" );
+ }
+ m.appendTail( buf );
+ buf.append( wrapperCloseTag );
mainText = null;
+ Document mainDoc = parseStrictOrSloppyXML( buf, mainDescription+" (wrapped)" );
+ buf.setLength( 0 );
String appendText = decodeText( appendStream, appendDescription ).text;
- appendText = xmlDeclPtn.matcher( appendText ).replaceFirst( "" );
- appendText = ""+ appendText +"";
- Document appendDoc = parseStrictOrSloppyXML( appendText, appendDescription+" (wrapped)" );
+ buf.ensureCapacity( wrapperOpenTag.length() + appendText.length() + wrapperCloseTag.length() );
+ buf.append( wrapperOpenTag );
+ m = comboPtn.matcher( appendText );
+ while ( m.find() ) {
+ m.appendReplacement( buf, "" );
+ }
+ m.appendTail( buf );
+ buf.append( wrapperCloseTag );
appendText = null;
+ Document appendDoc = parseStrictOrSloppyXML( buf, appendDescription+" (wrapped)" );
+ buf.setLength( 0 );
+
+ buf.trimToSize(); // Free the buffer.
+ buf = null;
XMLPatcher patcher = new XMLPatcher();
patcher.setGlobalPanic( globalPanic );
@@ -247,6 +297,20 @@ public class ModUtilities {
mainDoc = null;
appendDoc = null;
+ // Add FTL tags and move all content inside them.
+ // Collect live getContent() results in an Arraylist to avoid
+ // ConcurrentModificationException when detaching in the loop.
+ if ( mainHadRootTags ) {
+ Element mergedRoot = mergedDoc.getRootElement();
+ Element ftlNode = new Element( "FTL" );
+ List mergedContentList = new ArrayList( mergedRoot.getContent() );
+ for ( Content c : mergedContentList ) { //
+ c.detach();
+ }
+ ftlNode.addContent( mergedContentList );
+ mergedRoot.addContent( ftlNode );
+ }
+
// Bake XML into text, filtering the stream to standardize newlines and encode.
// TODO: sloppyPrint() needs EOL normalizing!?
//
@@ -704,7 +768,7 @@ public class ModUtilities {
""
) );
}
- m.appendReplacement( dstBuf, m.quoteReplacement(m.group( 2 ).replaceAll( "[^\n]", "" )) ); // Strip comments, but preserve line count.
+ m.appendReplacement( dstBuf, m.quoteReplacement( m.group( 2 ).replaceAll( "[^\n]", "" ) ) ); // Strip comments, but preserve line count.
}
m.appendTail( dstBuf );
tmpBuf = srcBuf; srcBuf = dstBuf; dstBuf = tmpBuf; dstBuf.setLength( 0 );
@@ -718,7 +782,7 @@ public class ModUtilities {
ReportMessage.ERROR,
"<"+ m.group( 1 ) +"...>..."+ m.group( 4 ) +">"
) );
- m.appendReplacement( dstBuf, m.quoteReplacement("<"+ m.group( 1 ) + m.group( 2 ) +">"+ m.group( 3 ) +""+ m.group( 1 ) +">") );
+ m.appendReplacement( dstBuf, m.quoteReplacement( "<"+ m.group( 1 ) + m.group( 2 ) +">"+ m.group( 3 ) +""+ m.group( 1 ) +">" ) );
}
}
m.appendTail( dstBuf );
@@ -744,7 +808,7 @@ public class ModUtilities {
ReportMessage.ERROR,
"..."
) );
- m.appendReplacement( dstBuf, m.quoteReplacement(m.group(1) +"") );
+ m.appendReplacement( dstBuf, m.quoteReplacement( m.group( 1 ) +"" ) );
}
m.appendTail( dstBuf );
tmpBuf = srcBuf; srcBuf = dstBuf; dstBuf = tmpBuf; dstBuf.setLength( 0 );
diff --git a/src/main/java/net/vhati/modmanager/ui/ModXMLSandbox.java b/src/main/java/net/vhati/modmanager/ui/ModXMLSandbox.java
index bc5724d..bb25d81 100644
--- a/src/main/java/net/vhati/modmanager/ui/ModXMLSandbox.java
+++ b/src/main/java/net/vhati/modmanager/ui/ModXMLSandbox.java
@@ -11,6 +11,7 @@ import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
+import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
@@ -63,7 +64,6 @@ import net.vhati.modmanager.core.SloppyXMLOutputProcessor;
import net.vhati.modmanager.core.XMLPatcher;
import net.vhati.modmanager.ui.ClipboardMenuMouseListener;
-import org.jdom2.Document;
import org.jdom2.JDOMException;
@@ -73,7 +73,7 @@ import org.jdom2.JDOMException;
public class ModXMLSandbox extends JFrame implements ActionListener {
private UndoManager undoManager = new UndoManager();
- private Document mainDoc = null;
+ private String mainText = null;
private File datsDir;
@@ -344,17 +344,11 @@ public class ModXMLSandbox extends JFrame implements ActionListener {
if ( innerPath == null ) return;
is = pack.getInputStream( innerPath );
- String mainText = ModUtilities.decodeText( is, pack.getName()+":"+innerPath ).text;
+ InputStream rebuiltStream = ModUtilities.rebuildXMLFile( is, "windows-1252", pack.getName()+":"+innerPath );
+ String rebuiltText = ModUtilities.decodeText( rebuiltStream, "Sandbox Main XML" ).text;
is.close();
- mainText = mainText.replaceFirst( "<[?]xml [^>]*?[?]>", "" );
- mainText = ""+ mainText +"";
- mainDoc = ModUtilities.parseStrictOrSloppyXML( mainText, "Sandbox Main XML" );
-
- StringWriter writer = new StringWriter();
- SloppyXMLOutputProcessor.sloppyPrint( mainDoc, writer, null );
- String displayedText = writer.toString().replaceAll( "\r(?!\n)|(?]*?[?]>", "" );
- appendText = ""+ appendText +"";
- Document appendDoc = ModUtilities.parseStrictOrSloppyXML( appendText, "Sandbox Append XML" );
+ InputStream appendStream = new ByteArrayInputStream( appendText.getBytes( "UTF-8" ) );
- XMLPatcher patcher = new XMLPatcher();
- patcher.setGlobalPanic( false );
- Document resultDoc = patcher.patch( mainDoc, appendDoc );
+ InputStream resultStream = ModUtilities.patchXMLFile( mainStream, appendStream, "windows-1252", false, "Sandbox Main XML", "Sandbox Append XML" );
+ String resultText = ModUtilities.decodeText( resultStream, "Sandbox Result XML" ).text;
- StringWriter writer = new StringWriter();
- SloppyXMLOutputProcessor.sloppyPrint( resultDoc, writer, null );
- String displayedText = writer.toString().replaceAll( "\r(?!\n)|(?