package org.juxtasoftware.model;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import org.juxtasoftware.service.importer.JuxtaXsltFactory;
import com.google.common.base.Objects;
public class JuxtaXslt extends WorkspaceMember {
private String name;
private String xslt;
private String defaultNamespace;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getXslt() {
return xslt;
}
public void setXslt(String xslt) {
this.xslt = xslt;
}
public String getDefaultNamespace() {
return defaultNamespace;
}
public void setDefaultNamespace(String defaultNamespace) {
this.defaultNamespace = defaultNamespace;
}
@Override
public String toString() {
return "JuxtaXslt [name=" + name + ", id=" + id + "]";
}
@Override
public boolean equals(Object obj) {
if (this.id != 0 && obj != null && obj instanceof JuxtaXslt) {
return this.id == ((JuxtaXslt) obj).id;
}
return super.equals(obj);
}
@Override
public int hashCode() {
return this.id == 0 ? super.hashCode() : Objects.hashCode(this.id);
}
/**
* Add an exclusion for a specific occurence of a tag. Only this instance will
* be excluded from the restlating witness. If the tag is alread globally
* excluded, this call does nothing
*
* @param qName
* @param occurrence
* @throws IOException
*/
public void addSingleExclusion(String qName, Integer occurrence) throws IOException {
// do nothing if the tag is already excluded
if ( isExcluded( qName, occurrence ) ) {
return;
}
// bound the regon of text to work with: everything beteen
// the single exclusion marker and the breaks marker
final String single = "<!--single-exclusions-->";
final String breaks = "<!--breaks-->";
final String matchKey = "match=\"";
final String testKey = "test=\"";
final String strOccurrence = Integer.toString(occurrence);
int pos = this.xslt.indexOf( single );
int limitPos = this.xslt.indexOf( breaks );
// see if there is already a single exclusion for this tag present
pos = this.xslt.indexOf(matchKey, pos)+matchKey.length();
while ( pos > -1 && pos < limitPos) {
int endPos = this.xslt.indexOf("\"", pos);
String tag = this.xslt.substring(pos,endPos);
if ( tag.equals(qName) ) {
// found a match. See if the specified occurrence is excluded
int testPos = this.xslt.indexOf(testKey, pos)+testKey.length();
int endTest = this.xslt.indexOf("\"", testPos);
String conditions = this.xslt.substring(testPos, endTest);
// strip out everything by a space separated list of occurrences
// generate a set and see if the taget number is present
String occurrences = conditions.replaceAll("(\\$count\\s+!=\\s+|\\s+and\\s+)", " ").trim();
Set<String> nums = new HashSet<String>( Arrays.asList(occurrences.split(" ")) );
if ( nums.contains( strOccurrence) ) {
// already present, nothing more to do
return;
} else {
// not present: add the occurrence to the conditions
conditions += " and $count != ";
conditions += strOccurrence;
this.xslt = this.xslt.substring(0,testPos)+conditions+this.xslt.substring(endTest);
return;
}
}
pos = this.xslt.indexOf(matchKey, endPos);
if ( pos > -1 ) {
pos += matchKey.length();
}
}
// if we got here, we need to add a new single exclusion for this tag occurrence.
// NOTE: use Matcher to avoid problems caused by the $ in the replacement ext
String xsltFrag = JuxtaXsltFactory.getSingleExclusionTemplate();
xsltFrag = xsltFrag.replaceAll("\\{TAG\\}", qName );
String condition = Matcher.quoteReplacement("$count != "+strOccurrence);
try {
xsltFrag = xsltFrag.replaceAll("\\{CONDITION\\}", condition );
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
pos = this.xslt.indexOf( single ) + single.length()+1;
this.xslt = this.xslt.substring(0,pos)+" "+xsltFrag+"\n"+this.xslt.substring(pos);
}
/**
* Add a global tag exclusion. All occurrences of this tag will be
* excluded when transforming the source into a witness.
*
* @param qName The tag to exclude. It must include namespace
*/
public void addGlobalExclusion(String qName) {
final String marker = "<!--global-exclusions-->";
int pos = this.xslt.indexOf(marker)+marker.length();
String ex = "\n <xsl:template match=\""+qName+"\"/>";
this.xslt = xslt.substring(0,pos)+ex+this.xslt.substring(pos);
// remove from breaks so there is not a template ambiguity
pos = xslt.indexOf("<!--breaks-->");
int limitPos = xslt.indexOf("<xsl:template match=\"text()\">");
pos = xslt.indexOf("match=\"", pos)+7;
if ( pos > limitPos ) {
return;
}
int endPos = xslt.indexOf("\"", pos);
String tags = xslt.substring(pos,endPos);
// remove the tag and clean up an double | that may remain
String regex = "("+qName+"\\||\\|"+qName+")";
regex = regex.replace("*", "\\*");
String newTags = tags.replaceAll(regex,"");
// sub the new tags list back into the xslt
this.xslt = this.xslt.replace(tags, newTags);
}
/**
* Returns true of this tag occurrence has been specifically excluded or
* the tag has been globally excluded
* @param tagName
* @return
*/
public boolean isExcluded( final String tagName, final int occurrence ) {
// first check the global exclude...
final String global = "<!--global-exclusions-->";
final String single = "<!--single-exclusions-->";
final String breaks = "<!--breaks-->";
final String matchKey = "match=\"";
int pos = xslt.indexOf( global );
int limitPos = xslt.indexOf( single );
pos = xslt.indexOf(matchKey, pos)+matchKey.length();
if ( pos < limitPos ) {
while ( pos > -1 && pos < limitPos) {
int endPos = xslt.indexOf("\"", pos);
String tag = xslt.substring(pos,endPos);
if ( tag.equals(tagName) ) {
return true;
}
pos = xslt.indexOf(matchKey, endPos);
if ( pos > -1 ) {
pos += matchKey.length();
}
}
}
// now see if it has been singly excluded. Valid search range is between
// single exclusion marker and breaks marker. Final all instances
// of 'match="' and see if the tag name matches
pos = xslt.indexOf( single );
limitPos = xslt.indexOf( breaks );
pos = xslt.indexOf(matchKey, pos)+matchKey.length();
if ( pos > limitPos ) {
return false;
}
while ( pos > -1 && pos < limitPos) {
int endPos = xslt.indexOf("\"", pos);
String tag = xslt.substring(pos,endPos);
if ( tag.equals(tagName) ) {
// found a match. See if the specified occurrence is excluded
if ( isOccurrenceInTest(pos, occurrence) ) {
return true;
}
}
// move on to next match (if any)
pos = xslt.indexOf(matchKey, endPos);
if ( pos > -1 ) {
pos += matchKey.length();
}
}
return false;
}
/**
* REturns TRUE if the specified occurrence of a tag should include a linebreak
* @param tagName
* @param occurrence
* @return
*/
public boolean hasLineBreak( final String tagName, final int occurrence ) {
// first, check for a GLOBAL linebreak for this tag: look at
// the content between the quotes after the breaks marker and before the
// text template match start
final String breaksMarker = "<!--breaks-->";
final String matchKey = "match=\"";
int pos = this.xslt.indexOf(breaksMarker);
int limitPos = this.xslt.indexOf("<xsl:template match=\"text()\">");
pos = this.xslt.indexOf(matchKey, pos)+matchKey.length();
if ( pos > limitPos ) {
return false;
}
int endPos = xslt.indexOf("\"", pos);
String tags = xslt.substring(pos,endPos);
// if its a * then everything has a linebreak
if ( tags.equals("*")) {
return true;
}
// split up by the | marker and compare names
String[] tagArray = tags.split("\\|");
for ( int i=0; i<tagArray.length; i++) {
String tag = tagArray[i];
if ( tag.equals(tagName) ) {
return true;
}
}
// nothing found yet, now check for single exclusions and linebreaking
final String singleMarker = "<!--single-exclusions-->";
pos = this.xslt.indexOf(singleMarker);
limitPos = this.xslt.indexOf(breaksMarker);
pos = this.xslt.indexOf(matchKey, pos)+matchKey.length();
if ( pos > limitPos ) {
// no single exclusions... done
return false;
}
// look at all instances of match= to see if the requested tag is found
while ( pos > -1 && pos < limitPos) {
endPos = this.xslt.indexOf("\"", pos);
String tag = this.xslt.substring(pos,endPos);
if ( tag.equals(tagName) ) {
// found a match. If the requested occurrence is listed,
// the occurrence is excluded and cannot have a linebreak
if ( isOccurrenceInTest(pos, occurrence)) {
return false;
} else {
// Occurrence is NOT excluded. See if has linebreaks applied
final String endIfMarker = "</xsl:if>";
final String lineBreakMarker = "$display-linebreak";
int endIfPos = this.xslt.indexOf(endIfMarker, endPos);
int lbPos = this.xslt.indexOf(lineBreakMarker, endPos);
if ( lbPos > -1 && lbPos < endIfPos ) {
return true;
} else {
return false;
}
}
}
// move on to next match (if any)
pos = this.xslt.indexOf(matchKey, endPos);
if ( pos > -1 ) {
pos += matchKey.length();
}
}
return false;
}
/**
* Test if the soecified occurrence number appears in the test clause of a single exclusion
* @param startPos
* @param occurrence
* @return
*/
private boolean isOccurrenceInTest( final int startPos, final int occurrence ) {
final String testKey = "test=\"";
int testPos = this.xslt.indexOf(testKey, startPos)+testKey.length();
int endTest = this.xslt.indexOf("\"", testPos);
String conditions = xslt.substring(testPos, endTest);
// strip out everything by a space separated list of occurrences
// generate a set and see if the taget number is present
conditions = conditions.replaceAll("(\\$count\\s+!=\\s+|\\s+and\\s+)", " ").trim();
Set<String> nums = new HashSet<String>( Arrays.asList(conditions.split(" ")) );
if ( nums.contains( Integer.toString(occurrence)) ) {
return true;
}
return false;
}
/**
* Strip out all single exclusions from this template
*/
public void stripSingleExclusions() {
final String single = "<!--single-exclusions-->";
int pos = this.xslt.indexOf(single)+single.length();
int endPos = this.xslt.indexOf("<!--breaks-->");
// +5 to preserve linefeed and formatting spaces
this.xslt = this.xslt.substring(0,pos+5)+this.xslt.substring(endPos);
}
}