Added XMLPatcher for fancy mod syntax; not wired to the Patch button yet
This commit is contained in:
parent
4eb46ea542
commit
6174e45645
1 changed files with 554 additions and 0 deletions
554
src/main/java/net/vhati/modmanager/core/XMLPatcher.java
Normal file
554
src/main/java/net/vhati/modmanager/core/XMLPatcher.java
Normal file
|
@ -0,0 +1,554 @@
|
|||
package net.vhati.modmanager.core;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.vhati.modmanager.core.SloppyXMLParser;
|
||||
|
||||
import org.jdom2.Attribute;
|
||||
import org.jdom2.Content;
|
||||
import org.jdom2.Document;
|
||||
import org.jdom2.Element;
|
||||
import org.jdom2.Namespace;
|
||||
import org.jdom2.filter.AbstractFilter;
|
||||
import org.jdom2.filter.ElementFilter;
|
||||
import org.jdom2.filter.Filter;
|
||||
import org.jdom2.input.JDOMParseException;
|
||||
import org.jdom2.input.SAXBuilder;
|
||||
|
||||
|
||||
/**
|
||||
* Programmatically edits existing XML with instructions from another XML doc.
|
||||
* Other tags are simply appended as-is.
|
||||
*/
|
||||
public class XMLPatcher {
|
||||
|
||||
protected Namespace modNS;
|
||||
protected Namespace modAppendNS;
|
||||
protected Namespace modOverwriteNS;
|
||||
|
||||
|
||||
public XMLPatcher() {
|
||||
modNS = Namespace.getNamespace( "mod", "mod" );
|
||||
modAppendNS = Namespace.getNamespace( "mod-append", "mod-append" );
|
||||
modOverwriteNS = Namespace.getNamespace( "mod-overwrite", "mod-overwrite" );
|
||||
}
|
||||
|
||||
|
||||
public Document patch( Document mainDoc, Document appendDoc ) {
|
||||
Document resultDoc = mainDoc.clone();
|
||||
Element resultRoot = resultDoc.getRootElement();
|
||||
Element appendRoot = appendDoc.getRootElement();
|
||||
|
||||
ElementFilter modFilter = new ElementFilter( modNS );
|
||||
for ( Content content : appendRoot.getContent() ) {
|
||||
if ( modFilter.matches( content ) ) {
|
||||
Element node = (Element)content;
|
||||
|
||||
boolean handled = false;
|
||||
List<Element> matchedNodes = handleModFind( resultRoot, node );
|
||||
if ( matchedNodes != null ) {
|
||||
handled = true;
|
||||
for ( Element matchedNode : matchedNodes ) {
|
||||
handleModCommands( matchedNode, node );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !handled ) {
|
||||
throw new IllegalArgumentException( String.format( "Unrecognized mod tag <%s> (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
}
|
||||
}
|
||||
else {
|
||||
resultRoot.addContent( content.clone() );
|
||||
}
|
||||
}
|
||||
|
||||
return resultDoc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns find results if node is a find tag, or null if it's not.
|
||||
*
|
||||
* An empty list will be returned if there were no matches.
|
||||
*
|
||||
* TODO: Throw an exception in callers if results are required.
|
||||
*/
|
||||
protected List<Element> handleModFind( Element contextNode, Element node ) {
|
||||
List<Element> result = null;
|
||||
|
||||
if ( node.getNamespace().equals( modNS ) ) {
|
||||
|
||||
if ( node.getName().equals( "findName" ) ) {
|
||||
|
||||
String searchName = node.getAttributeValue( "name" );
|
||||
boolean searchReverse = getAttributeBooleanValue( node, "reverse", true );
|
||||
int searchStart = getAttributeIntValue( node, "start", 0 );
|
||||
int searchLimit = getAttributeIntValue( node, "limit", 1 );
|
||||
|
||||
if ( searchName == null || searchName.length() == 0 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> requires a name attribute (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
if ( searchStart < 0 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> 'start' attribute is not >= 0 (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
if ( searchLimit < -1 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> 'limit' attribute is not >= -1 (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
|
||||
Map<String,String> attrMap = new HashMap<String,String>();
|
||||
attrMap.put( "name", searchName );
|
||||
LikeFilter searchFilter = new LikeFilter( null, attrMap, null );
|
||||
|
||||
List<Element> matchedNodes = new ArrayList<Element>( contextNode.getContent( searchFilter ) );
|
||||
if ( searchReverse ) Collections.reverse( matchedNodes );
|
||||
|
||||
if ( searchStart < matchedNodes.size() ) {
|
||||
if ( searchLimit > -1 ) {
|
||||
matchedNodes = matchedNodes.subList( searchStart, Math.max( matchedNodes.size(), searchStart + searchLimit ) );
|
||||
} else if ( searchStart > 0 ) {
|
||||
matchedNodes = matchedNodes.subList( searchStart, matchedNodes.size() );
|
||||
}
|
||||
}
|
||||
|
||||
result = matchedNodes;
|
||||
}
|
||||
else if ( node.getName().equals( "findLike" ) ) {
|
||||
|
||||
String searchType = node.getAttributeValue( "type" );
|
||||
boolean searchReverse = getAttributeBooleanValue( node, "reverse", false );
|
||||
int searchStart = getAttributeIntValue( node, "start", 0 );
|
||||
int searchLimit = getAttributeIntValue( node, "limit", -1 );
|
||||
|
||||
if ( searchType != null && searchType.length() == 0 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> type attribute, when present, can't be empty (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
if ( searchStart < 0 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> 'start' attribute is not >= 0 (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
if ( searchLimit < -1 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> 'limit' attribute is not >= -1 (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
|
||||
Map<String,String> attrMap = new HashMap<String,String>();
|
||||
String searchValue = null;
|
||||
|
||||
Element selectorNode = node.getChild( "selector", modNS );
|
||||
if ( selectorNode != null ) {
|
||||
for ( Attribute attr : selectorNode.getAttributes() ) {
|
||||
if ( attr.getNamespace().equals( Namespace.NO_NAMESPACE ) ) {
|
||||
// Blank element values can't be detected as different from absent values (never null).
|
||||
// Forbid "" attributes for consistency. :/
|
||||
if ( attr.getValue().length() == 0 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> attributes, when present, can't be empty (%s).", selectorNode.getName(), getPathToRoot(selectorNode) ) );
|
||||
|
||||
attrMap.put( attr.getName(), attr.getValue() );
|
||||
}
|
||||
}
|
||||
searchValue = selectorNode.getTextTrim(); // Never null, but often "".
|
||||
if ( searchValue.length() > 0 ) searchValue = null;
|
||||
}
|
||||
|
||||
LikeFilter searchFilter = new LikeFilter( searchType, attrMap, searchValue );
|
||||
|
||||
List<Element> matchedNodes = new ArrayList<Element>( contextNode.getContent( searchFilter ) );
|
||||
if ( searchReverse ) Collections.reverse( matchedNodes );
|
||||
|
||||
if ( searchStart < matchedNodes.size() ) {
|
||||
if ( searchLimit > -1 ) {
|
||||
matchedNodes = matchedNodes.subList( searchStart, Math.max( matchedNodes.size(), searchStart + searchLimit ) );
|
||||
} else if ( searchStart > 0 ) {
|
||||
matchedNodes = matchedNodes.subList( searchStart, matchedNodes.size() );
|
||||
}
|
||||
}
|
||||
|
||||
result = matchedNodes;
|
||||
}
|
||||
else if ( node.getName().equals( "findWithChildLike" ) ) {
|
||||
|
||||
String searchType = node.getAttributeValue( "type" );
|
||||
String searchChildType = node.getAttributeValue( "child-type" );
|
||||
boolean searchReverse = getAttributeBooleanValue( node, "reverse", false );
|
||||
int searchStart = getAttributeIntValue( node, "start", 0 );
|
||||
int searchLimit = getAttributeIntValue( node, "limit", -1 );
|
||||
|
||||
if ( searchType != null && searchType.length() == 0 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> type attribute, when present, can't be empty (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
if ( searchChildType != null && searchChildType.length() == 0 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> child-type attribute, when present, can't be empty (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
if ( searchStart < 0 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> 'start' attribute is not >= 0 (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
if ( searchLimit < -1 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> 'limit' attribute is not >= -1 (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
|
||||
Map<String,String> attrMap = new HashMap<String,String>();
|
||||
String searchValue = null;
|
||||
|
||||
Element selectorNode = node.getChild( "selector", modNS );
|
||||
if ( selectorNode != null ) {
|
||||
for ( Attribute attr : selectorNode.getAttributes() ) {
|
||||
if ( attr.getNamespace().equals( Namespace.NO_NAMESPACE ) ) {
|
||||
// TODO: Forbid "" attributes, because blank value doesn't work?
|
||||
attrMap.put( attr.getName(), attr.getValue() );
|
||||
}
|
||||
}
|
||||
searchValue = selectorNode.getTextTrim(); // Never null, but often "".
|
||||
if ( searchValue.length() > 0 ) searchValue = null;
|
||||
}
|
||||
|
||||
LikeFilter searchChildFilter = new LikeFilter( searchChildType, attrMap, searchValue );
|
||||
WithChildFilter searchFilter = new WithChildFilter( searchType, searchChildFilter );
|
||||
|
||||
List<Element> matchedNodes = new ArrayList<Element>( contextNode.getContent( searchFilter ) );
|
||||
if ( searchReverse ) Collections.reverse( matchedNodes );
|
||||
|
||||
if ( searchStart < matchedNodes.size() ) {
|
||||
if ( searchLimit > -1 ) {
|
||||
matchedNodes = matchedNodes.subList( searchStart, Math.max( matchedNodes.size(), searchStart + searchLimit ) );
|
||||
} else if ( searchStart > 0 ) {
|
||||
matchedNodes = matchedNodes.subList( searchStart, matchedNodes.size() );
|
||||
}
|
||||
}
|
||||
|
||||
result = matchedNodes;
|
||||
}
|
||||
else if ( node.getName().equals( "findComposite" ) ) {
|
||||
|
||||
boolean searchReverse = getAttributeBooleanValue( node, "reverse", false );
|
||||
int searchStart = getAttributeIntValue( node, "start", 0 );
|
||||
int searchLimit = getAttributeIntValue( node, "limit", -1 );
|
||||
|
||||
if ( searchStart < 0 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> 'start' attribute is not >= 0 (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
if ( searchLimit < -1 )
|
||||
throw new IllegalArgumentException( String.format( "<%s> 'limit' attribute is not >= -1 (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
|
||||
Element parNode = node.getChild( "par", modNS );
|
||||
if ( parNode == null )
|
||||
throw new IllegalArgumentException( String.format( "<%s> requires a <par> tag (%s).", node.getName(), getPathToRoot(node) ) );
|
||||
|
||||
List<Element> matchedNodes = handleModPar( contextNode, parNode );
|
||||
if ( searchReverse ) Collections.reverse( matchedNodes );
|
||||
|
||||
if ( searchStart < matchedNodes.size() ) {
|
||||
if ( searchLimit > -1 ) {
|
||||
matchedNodes = matchedNodes.subList( searchStart, Math.max( matchedNodes.size(), searchStart + searchLimit ) );
|
||||
} else if ( searchStart > 0 ) {
|
||||
matchedNodes = matchedNodes.subList( searchStart, matchedNodes.size() );
|
||||
}
|
||||
}
|
||||
|
||||
result = matchedNodes;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns collated find results (and par results, handled recursively), or null if node wasn't a par.
|
||||
*
|
||||
* Unique results from all finds will be combined and sorted in the order they appear under contextNode.
|
||||
*/
|
||||
protected List<Element> handleModPar( Element contextNode, Element node ) {
|
||||
List<Element> result = null;
|
||||
|
||||
if ( node.getNamespace().equals( modNS ) ) {
|
||||
|
||||
if ( node.getName().equals( "par" ) ) {
|
||||
|
||||
String parOp = node.getAttributeValue( "op" );
|
||||
|
||||
if ( parOp == null || (!parOp.equals("AND") && !parOp.equals("OR")) )
|
||||
throw new IllegalArgumentException( String.format( "Invalid \"op\" attribute (%s). Must be 'AND' or 'OR'.", getPathToRoot(node) ) );
|
||||
|
||||
boolean isAnd = parOp.equals("AND");
|
||||
boolean isOr = parOp.equals("OR");
|
||||
|
||||
Set<Element> candidateSet = new HashSet<Element>();
|
||||
for ( Element criteriaNode : node.getChildren() ) {
|
||||
List<Element> candidates;
|
||||
if ( criteriaNode.getName().equals( "par" ) && criteriaNode.getNamespace().equals( modNS ) ) {
|
||||
candidates = handleModPar( contextNode, criteriaNode );
|
||||
} else {
|
||||
candidates = handleModFind( contextNode, criteriaNode );
|
||||
if ( candidates == null )
|
||||
throw new IllegalArgumentException( String.format( "Invalid <par> search criteria <%s> (%s). Must be a <find...> or <par>.", criteriaNode.getName(), getPathToRoot(criteriaNode) ) );
|
||||
}
|
||||
|
||||
if ( isOr || candidateSet.isEmpty() ) {
|
||||
candidateSet.addAll( candidates );
|
||||
}
|
||||
else if ( isAnd ) {
|
||||
candidateSet.retainAll( candidates );
|
||||
}
|
||||
}
|
||||
Map<Integer,Element> orderedCandidateMap = new TreeMap<Integer,Element>();
|
||||
for ( Element candidate : candidateSet ) {
|
||||
int index = contextNode.indexOf( candidate );
|
||||
orderedCandidateMap.put( new Integer(index), candidate );
|
||||
}
|
||||
|
||||
List<Element> matchedNodes = new ArrayList<Element>( orderedCandidateMap.values() );
|
||||
|
||||
result = matchedNodes;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs child mod-commands under node, against contextNode.
|
||||
*
|
||||
* TODO: Maybe have handleModCommand() returning null when unrecognized,
|
||||
* or an object with flags to continue or stop looping commands at
|
||||
* contextNode (e.g., halting after removeTag).
|
||||
*/
|
||||
protected void handleModCommands( Element contextNode, Element node ) {
|
||||
|
||||
for ( Element cmdNode : node.getChildren() ) {
|
||||
boolean handled = false;
|
||||
|
||||
if ( cmdNode.getNamespace().equals( modNS ) ) {
|
||||
|
||||
// Handle nested finds.
|
||||
List<Element> matchedNodes = handleModFind( contextNode, cmdNode );
|
||||
if ( matchedNodes != null ) {
|
||||
handled = true;
|
||||
for ( Element matchedNode : matchedNodes ) {
|
||||
handleModCommands( matchedNode, cmdNode );
|
||||
}
|
||||
}
|
||||
else if ( cmdNode.getName().equals( "selector" ) ) {
|
||||
handled = true;
|
||||
// No-op.
|
||||
}
|
||||
else if ( cmdNode.getName().equals( "par" ) ) {
|
||||
handled = true;
|
||||
// No-op.
|
||||
}
|
||||
else if ( cmdNode.getName().equals( "setAttributes" ) ) {
|
||||
handled = true;
|
||||
for ( Attribute attrib : cmdNode.getAttributes() ) {
|
||||
contextNode.setAttribute( attrib.clone() );
|
||||
}
|
||||
}
|
||||
else if ( cmdNode.getName().equals( "setValue" ) ) {
|
||||
handled = true;
|
||||
contextNode.setText( cmdNode.getTextTrim() );
|
||||
}
|
||||
else if ( cmdNode.getName().equals( "removeTag" ) ) {
|
||||
handled = true;
|
||||
contextNode.detach();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
else if ( cmdNode.getNamespace().equals( modAppendNS ) ) {
|
||||
// Append cmdNode (sans namespace) to the contextNode.
|
||||
handled = true;
|
||||
|
||||
Element newNode = cmdNode.clone();
|
||||
newNode.setNamespace( null );
|
||||
contextNode.addContent( newNode );
|
||||
}
|
||||
|
||||
else if ( cmdNode.getNamespace().equals( modOverwriteNS ) ) {
|
||||
// Remove the first child with the same type and insert cmdNode at its position.
|
||||
// Or just append if nothing was replaced.
|
||||
handled = true;
|
||||
|
||||
Element newNode = cmdNode.clone();
|
||||
newNode.setNamespace( null );
|
||||
|
||||
Element doomedNode = contextNode.getChild( cmdNode.getName(), null );
|
||||
if ( doomedNode != null ) {
|
||||
int doomedIndex = contextNode.indexOf( doomedNode );
|
||||
doomedNode.detach();
|
||||
contextNode.addContent( doomedIndex, newNode );
|
||||
}
|
||||
else {
|
||||
contextNode.addContent( newNode );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !handled ) {
|
||||
throw new IllegalArgumentException( String.format( "Unrecognized mod tag <%s> (%s).", cmdNode.getName(), getPathToRoot(cmdNode) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string describing this element's location.
|
||||
*
|
||||
* Example: /root/event(SOME_NAME)/choice/text
|
||||
*/
|
||||
protected String getPathToRoot( Element node ) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
String chunk;
|
||||
String tmp;
|
||||
while ( node != null ) {
|
||||
chunk = "/"+ node.getName();
|
||||
|
||||
tmp = node.getAttributeValue( "name" );
|
||||
if ( tmp != null && tmp.length() > 0 )
|
||||
chunk += "("+ tmp +")";
|
||||
|
||||
buf.insert( 0, chunk );
|
||||
node = node.getParentElement();
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the boolean value of an attribute, or a default when the attribute is null.
|
||||
* Only 'true' and 'false' are accepted.
|
||||
*/
|
||||
protected boolean getAttributeBooleanValue( Element node, String attrName, boolean defaultValue ) {
|
||||
String tmp = node.getAttributeValue( attrName );
|
||||
if ( tmp == null ) return defaultValue;
|
||||
|
||||
if ( tmp.equals( "true" ) ) {
|
||||
return true;
|
||||
} else if ( tmp.equals( "false" ) ) {
|
||||
return false;
|
||||
} else {
|
||||
throw new IllegalArgumentException( String.format( "Invalid boolean attribute \"%s\" (%s). Must be 'true' or 'false'.", attrName, getPathToRoot(node) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the int value of an attribute, or a default when the attribute is null.
|
||||
*/
|
||||
protected int getAttributeIntValue( Element node, String attrName, int defaultValue ) {
|
||||
String tmp = node.getAttributeValue( attrName );
|
||||
if ( tmp == null ) return defaultValue;
|
||||
try {
|
||||
return Integer.parseInt( tmp );
|
||||
}
|
||||
catch ( NumberFormatException e ) {
|
||||
throw new IllegalArgumentException( String.format( "Invalid int attribute \"%s\" (%s).", attrName, getPathToRoot(node) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Matches elements with equal type/attributes/value.
|
||||
* Null args are ignored. A blank type or value arg is ignored.
|
||||
* All given attributes must be present on a candidate to match.
|
||||
* Attribute values in the map must not be null.
|
||||
*/
|
||||
protected static class LikeFilter extends AbstractFilter<Element> {
|
||||
private String type = null;;
|
||||
private Map<String,String> attrMap = null;
|
||||
private String value = null;
|
||||
|
||||
public LikeFilter( String type, Element selectorNode ) {
|
||||
this.type = type;
|
||||
|
||||
if ( selectorNode.hasAttributes() ) {
|
||||
this.attrMap = new HashMap<String,String>();
|
||||
for ( Attribute attr : selectorNode.getAttributes() ) {
|
||||
attrMap.put( attr.getName(), attr.getValue() );
|
||||
}
|
||||
}
|
||||
|
||||
this.value = selectorNode.getTextTrim();
|
||||
if ( this.value.length() == 0 ) this.value = null;
|
||||
}
|
||||
|
||||
public LikeFilter( String type, Map<String,String> attrMap, String value ) {
|
||||
if ( type != null && type.length() == 0 ) type = null;
|
||||
if ( value != null && value.length() == 0 ) value = null;
|
||||
|
||||
this.type = type;
|
||||
this.attrMap = attrMap;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element filter( Object content ) {
|
||||
if ( content instanceof Element == false ) return null;
|
||||
Element node = (Element)content;
|
||||
String tmp;
|
||||
|
||||
if ( type != null ) {
|
||||
if ( type.equals( node.getName() ) == false ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ( attrMap != null ) {
|
||||
for ( Map.Entry<String,String> entry : attrMap.entrySet() ) {
|
||||
String attrName = entry.getKey();
|
||||
String attrValue = entry.getValue();
|
||||
tmp = node.getAttributeValue( attrName );
|
||||
|
||||
if ( attrValue.equals( tmp ) == false ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( value != null ) {
|
||||
if ( value.equals( node.getTextTrim() ) == false ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Matches elements with child elements that match a filter.
|
||||
* If the filter is null, matches all elements with children.
|
||||
*/
|
||||
protected static class WithChildFilter extends AbstractFilter<Element> {
|
||||
private String type;
|
||||
private Filter childFilter;
|
||||
|
||||
public WithChildFilter( Filter childFilter ) {
|
||||
this( null, childFilter );
|
||||
}
|
||||
|
||||
public WithChildFilter( String type, Filter childFilter ) {
|
||||
this.type = type;
|
||||
this.childFilter = childFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element filter( Object content ) {
|
||||
if ( content instanceof Element == false ) return null;
|
||||
Element node = (Element)content;
|
||||
|
||||
if ( type != null ) {
|
||||
if ( type.equals( node.getName() ) == false ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ( childFilter != null ) {
|
||||
if ( node.getContent( childFilter ).isEmpty() )
|
||||
return null;
|
||||
}
|
||||
else if ( node.getChildren().isEmpty() ) {
|
||||
return null;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue