package outputter.process; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import org.apache.log4j.Logger; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.xpath.XPath; import outputter.Utilities; import outputter.XML2EQ; import outputter.data.CompositeEntity; import outputter.data.Entity; import outputter.data.EntityProposals; import outputter.data.FormalConcept; import outputter.data.NegatedQuality; import outputter.data.Quality; import outputter.data.QualityProposals; import outputter.data.RelationalQuality; import outputter.data.SimpleEntity; import outputter.knowledge.Dictionary; import outputter.knowledge.PermittedRelations; import outputter.knowledge.TermOutputerUtilities; import outputter.search.EntitySearcherOriginal; import outputter.search.TermSearcher; /** * This Strategy checks whether one <structure> is a quality (relational or not). * If true will create qualities accordingly. * This class also adjust underlying xml file by detaching the structure. * */ public class Structure2Quality implements AnnotationStrategy{ private static final Logger LOGGER = Logger.getLogger(Structure2Quality.class); Element root; String relation; String structname; String structid; boolean negation; // if true, negate the relation string boolean fromcharacterstatement; ArrayList<QualityProposals> qualities = new ArrayList<QualityProposals>(); //typically has 1 element, declared to be an arraylist for some rare cases (like 3 entities contact one another) ArrayList<EntityProposals> primaryentities = new ArrayList<EntityProposals>(); //if find a relational quality, the primaryentities are used at the left side of the relation. ArrayList<EntityProposals> spatialmodifier = null; private TermOutputerUtilities ontoutil; //static XPath pathCharacterUnderStucture; XPath pathrelationfromStructure; ArrayList<Entity> bilateral = new ArrayList<Entity>(); ArrayList<EntityProposals> keyentities; HashSet<String> identifiedqualities;//list of unique xml id of the structures found to be quality List<Element> detach_characters = new ArrayList<Element>(); /*static{ try{ pathCharacterUnderStucture = XPath.newInstance(".//character"); }catch (Exception e){ LOGGER.error("", e); } }*/ public Structure2Quality(Element root,String structurename, String structureid, ArrayList<EntityProposals> keyentities) { this.root = root; this.structname = structurename; this.structid = structureid; this.keyentities = keyentities; identifiedqualities = new HashSet<String>(); //list of unique xml id of the structures found to be quality } public void handle() { try { if(!isPossibleQuality()) return; //whether to include spatial constraint in the search for quality //first, try include //if failed, try exclude QualityProposals relationalquality = PermittedRelations.matchInPermittedRelation(structname, false,1); if(relationalquality!=null) parseforQuality(this.structname, this.structid); else{ ArrayList<Quality> quality= searchForSimpleQuality(this.structname); if(quality!=null) parseforQuality(this.structname, this.structid); else{ if(removeSpatialConstraint(this.structid)){ parseforQuality(this.structname, this.structid); //to see if the structure is a quality (relational or other quality) } } } //if(removeSpatialConstraint(this.structid)){ // parseforQuality(this.structname, this.structid); //to see if the structure is a quality (relational or other quality) //} } catch (JDOMException e) { LOGGER.error("", e); } } /** * remove the constraint part of the structure name that is a spatial term * @param structid * @return boolean success or not * @throws JDOMException */ private boolean removeSpatialConstraint(String structid) throws JDOMException { Element structure = (Element) XPath.selectSingleNode(root, ".//structure[@id='"+structid+"']"); String constraint = structure.getAttributeValue("constraint"); if((constraint!=null)&& constraint.matches(Dictionary.spatialtermptn)) { this.structname = structure.getAttributeValue("name"); LOGGER.debug("Structure2Quality calls EntitySearcherOriginal to search structure constraint '"+constraint+"'"); //this.spatialmodifier = new EntitySearcherOriginal().searchEntity(root, "", structure.getAttributeValue("constraint"), "", "",""); ArrayList<FormalConcept> results = new TermSearcher().searchTerm(constraint, "entity"); //simple search if(results!=null && results.size()>0){ EntityProposals ep = new EntityProposals(); for(FormalConcept fc: results){ SimpleEntity se = (SimpleEntity)fc; ep.setPhrase(constraint); ep.add(se); } this.spatialmodifier = new ArrayList<EntityProposals>(); Utilities.addEntityProposals(spatialmodifier, ep); } return true; } return false; } private boolean isPossibleQuality() throws JDOMException { Element structure = (Element) XPath.selectSingleNode(root, ".//structure[@id='"+this.structid+"']"); //if structure has constraint but the constraint is not a spatial modifier, then this can not be a quality. for example: parasymphysial plate (should not match plate-like) if((structure.getAttributeValue("constraint")!=null && !structure.getAttributeValue("constraint").matches(Dictionary.spatialtermptn))){ return false; } //if the full structurename (constraint+name) matches spatial term pattern (e.g.'dorsal surface'), then this can not be a quality if(this.structname.matches("^("+Dictionary.spatialtermptn+")+\\s+("+Dictionary.allSpatialHeadNouns()+")+")){ return false; } return true; } /** * call this (to detach all identifiedqualities) only when the quality/structure conflict is resolved: */ public void cleanHandledStructures(){ try{ for(String structid: identifiedqualities){ Element structure = (Element) XPath.selectSingleNode(root, ".//structure[@id='"+structid+"']"); if(structure!=null) structure.detach(); //identifiedqualities are used to check the relations this structure is involved in, //and the relations are needed for other purpose, //so don't detach relation here. } }catch (JDOMException e) { LOGGER.error("", e); } } @SuppressWarnings("unchecked") public void parseforQuality(String quality, String qualityid) throws JDOMException { // characters => quality // get quality candidate // handle this later => use below code to look and use all the // characters under a structure List<Element> characters = new ArrayList<Element>(); Element Structures,chara_detach=null; boolean negated = false; String qualitycopy=quality; XPath structurewithstructid = XPath.newInstance(".//structure[@id='"+ qualityid + "']"); Structures = (Element) structurewithstructid.selectSingleNode(this.root); //characters = pathCharacterUnderStucture.selectNodes(Structures); characters = Structures.getChildren("character"); //characters are checked to find out if the quality should be negated for (Element chara : characters) { String modifier = chara.getAttribute("modifier")!=null? chara.getAttributeValue("modifier"): ""; String value = chara.getAttribute("value")!=null? chara.getAttributeValue("value"):""; if ((modifier.startsWith("not") && !value.matches(Dictionary.negation)) || (!modifier.startsWith("not") && value.matches(Dictionary.negation))) { negated = true; chara_detach =chara; break; } } // is the candidate a relational quality? QualityProposals relationalquality = PermittedRelations.matchInPermittedRelation(quality, false,1); // TODO:// deal// with// negated// quality// later if (relationalquality != null) { XPath pathrelationfromStructure = XPath.newInstance(".//relation[@from='" + qualityid + "']"); List<Element> relations= pathrelationfromStructure.selectNodes(this.root); XPath structurewithstructid1; ArrayList<EntityProposals> Relatedentity; //If two entities are there, then the first one is the primary entity and the second one is the related entity if((relations!=null)&&(relations.size()>0)) { //Check whether this tostructure is a quality, if not create a related Entity for(Element relation:relations) { String tostructid = relation.getAttributeValue("to").trim(); if(tostructid.indexOf(" ")>0){ //>1 id: o510 o511, in case of relation="between" QualityProposals qproposals = new QualityProposals(); String[] toids = tostructid.split("\\s+"); int count = 0; for(String toid: toids){ structurewithstructid1 = XPath.newInstance(".//structure[@id='"+ toid.trim() + "']"); Element tostruct = (Element) structurewithstructid1.selectSingleNode(this.root); String tostructname = Utilities.getStructureName(root, toid); Relatedentity = new EntitySearcherOriginal().searchEntity(root, tostructname, tostructname, "", tostructname,""); if(Relatedentity!=null){ if(count==0){ this.primaryentities.addAll(Relatedentity); //set the first be the primary if(this.keyentities==null){ this.keyentities = new ArrayList<EntityProposals>(); this.keyentities.addAll(Relatedentity); } }else{ //set others as related entity for(EntityProposals relatedentity: Relatedentity){ RelationalQuality rq = new RelationalQuality(relationalquality, relatedentity); qproposals.add(rq); //need fix: these rqs don't belong to the same QualityProposal!, should be A in_contact_with B and C and D qproposals.setPhrase(quality); //this.qualities.add(qproposals); Utilities.addQualityProposals(qualities, qproposals); this.identifiedqualities.add(qualityid); } } } count++; } if(chara_detach!=null) chara_detach.detach(); }else{//one id structurewithstructid1 = XPath.newInstance(".//structure[@id='"+ tostructid.trim() + "']"); Element tostruct = (Element) structurewithstructid1.selectSingleNode(this.root); String tostructname = Utilities.getStructureName(root, tostructid); Relatedentity = new EntitySearcherOriginal().searchEntity(root, tostructid, tostructname, "", tostructname,""); if(Relatedentity!=null) { for(EntityProposals relatedentity: Relatedentity){ RelationalQuality rq = new RelationalQuality(relationalquality, relatedentity); QualityProposals qproposals = new QualityProposals(); qproposals.add(rq); qproposals.setPhrase(quality); //this.qualities.add(qproposals); Utilities.addQualityProposals(qualities, qproposals); this.identifiedqualities.add(qualityid); } if(chara_detach!=null) chara_detach.detach(); } //What are the primaryentities here? } } return; }else if((this.keyentities!=null) && (this.keyentities.size()>=2)) { for(int i=1;i<this.keyentities.size();i++) { RelationalQuality rq = new RelationalQuality(relationalquality, this.keyentities.get(i)); QualityProposals qproposals = new QualityProposals(); qproposals.add(rq); qproposals.setPhrase(quality); //this.qualities.add(qproposals); Utilities.addQualityProposals(qualities, qproposals); this.identifiedqualities.add(qualityid); } this.primaryentities.add(this.keyentities.get(0)); //if keyentities = [A, B, C, D] then primaryentity=A; //qualities=[relationalquality B; relationalquality C;relationalquality D] if(chara_detach!=null) chara_detach.detach(); return; } else if((this.keyentities!=null) && (this.keyentities.size()==1) && (checkbilateral()==true)) //bilateral structures? { //Here check bilateral will return us a list of entities which are bilateral.We pass it to relationalentitystrategy1 //to find related entities and primary entities for(Entity e:this.bilateral) { RelationalEntityStrategy re = new RelationalEntityStrategy(e); re.handle(); Hashtable<String,ArrayList<EntityProposals>> entities = re.getEntities(); ArrayList<EntityProposals> relatedentities = entities.get("Related Entities"); if((relatedentities!=null)&&(relatedentities.size()>0))//Single key entity might be a bilateral { for(int i=0;i<relatedentities.size();i++) { RelationalQuality rq = new RelationalQuality(relationalquality, relatedentities.get(i)); QualityProposals qproposals = new QualityProposals(); qproposals.add(rq); qproposals.setPhrase(quality); //this.qualities.add(qproposals); Utilities.addQualityProposals(qualities, qproposals); this.identifiedqualities.add(qualityid); } this.primaryentities.addAll(entities.get("Primary Entity")); if(chara_detach!=null) chara_detach.detach(); } } return; } else //pack and return an incomplete relational quality (EntityProposals = null) { RelationalQuality rq = new RelationalQuality(relationalquality, new EntityProposals()); QualityProposals qproposals = new QualityProposals(); qproposals.add(rq); qproposals.setPhrase(quality); //this.qualities.add(qproposals); Utilities.addQualityProposals(qualities, qproposals); this.identifiedqualities.add(qualityid); } return; } // may need to consider constraints, which may provide a related entity // not a relational quality, is this a simple quality or a negated //simple quality == quality character value + quality // quality? if((characters!=null)&&(characters.size()>0)) { for(Element chara:characters) checkForSimpleQuality(chara,quality,qualityid,negated,chara_detach); } else checkForSimpleQuality(null,quality,qualityid,negated,chara_detach); //detach_character(); need to be invoked only when S2Q is being accpeted by the calling function return; } public void detach_character() { for(Element chara:this.detach_characters) chara.detach(); } private boolean checkbilateral() { boolean bilateralcheck = false; for(Entity e:this.keyentities.get(0).getProposals()) { if(e.getId()!=null) { if(e instanceof SimpleEntity) { if(XML2EQ.elk.lateralsidescache.get(e.getPrimaryEntityLabel())!=null) { this.bilateral.add(e); bilateralcheck=true; } } else { for(Entity e1:((CompositeEntity) e).getEntities()) { if(e1.getPrimaryEntityLabel()!=null)//Entities which don't have a perfect match in ontology need not be checked for bilateral { if(XML2EQ.elk.lateralsidescache.get(e1.getPrimaryEntityLabel())!=null) { this.bilateral.add(e); bilateralcheck=true; break; } } } } } } return bilateralcheck; } //a separate function is created to handle structures(quality) with characters and without characters @SuppressWarnings("unchecked") private void checkForSimpleQuality(Element chara, String quality, String qualityid, boolean negated, Element chara_detach) { if(chara!=null){ quality=chara.getAttributeValue("value")+" "+quality; //large + 'expansion' } quality=quality.trim(); ArrayList<Quality> results = searchForSimpleQuality(quality); if(chara!=null){ //results now hold different qualities, not proposals for the same quality ArrayList<FormalConcept> results2 =new TermSearcher().searchTerm(chara.getAttributeValue("value"), "quality"); if(results2!=null){ if(results==null) results = new ArrayList<Quality>(); results.addAll((Collection<? extends Quality>) results2); } } if (results != null) { if (negated) { for(Quality result: results){ String t; String[] parentinfo = TermOutputerUtilities.retreiveParentInfoFromPATO(result.getId()); Quality parentquality = new Quality(); parentquality.setSearchString(""); parentquality.setString(parentinfo[1]); parentquality.setLabel(parentinfo[1]); parentquality.setId(parentinfo[0]); QualityProposals qproposals = new QualityProposals();//results hold different qualities, not proposals for the same quality NegatedQuality nquality = new NegatedQuality(result, parentquality); qproposals.add(nquality); qproposals.setPhrase(nquality.getString()); //this.qualities.add(qproposals); Utilities.addQualityProposals(qualities, qproposals); } this.identifiedqualities.add(qualityid); //to remove negated character and prevent from processed in the future this.detach_characters.add(chara_detach); } else { for(Quality result: results){ QualityProposals qproposals = new QualityProposals(); qproposals.setPhrase(result.getString()); qproposals.add(result); Utilities.addQualityProposals(qualities, qproposals); } //this.qualities.add(qproposals); this.identifiedqualities.add(qualityid); } if(chara!=null) this.detach_characters.add(chara); } } /** * * @param quality: modifier/character + structure/quality, for example, elongate rod * @return */ private ArrayList<Quality> searchForSimpleQuality(String quality) { ArrayList<FormalConcept> result; TermSearcher ts = new TermSearcher(); for(;;) { result = ts.searchTerm(quality, "quality"); if((result!=null)||quality.length()==0) break; quality =(quality.indexOf(" ")!=-1)?quality.substring(quality.indexOf(" ")).trim():""; } if(result!=null){ ArrayList<Quality> qualities = new ArrayList<Quality>(); for(FormalConcept fc: result) qualities.add((Quality)fc); return qualities; }else{ return null; } } }