package ca.concordia.cssanalyser.cssmodel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.w3c.dom.Document;
import ca.concordia.cssanalyser.cssmodel.declaration.Declaration;
import ca.concordia.cssanalyser.cssmodel.declaration.ShorthandDeclaration;
import ca.concordia.cssanalyser.cssmodel.media.MediaQueryList;
import ca.concordia.cssanalyser.cssmodel.selectors.BaseSelector;
import ca.concordia.cssanalyser.cssmodel.selectors.GroupingSelector;
import ca.concordia.cssanalyser.cssmodel.selectors.Selector;
import ca.concordia.cssanalyser.dom.DOMNodeWrapper;
import ca.concordia.cssanalyser.dom.DOMNodeWrapperList;
import ca.concordia.cssanalyser.refactoring.dependencies.CSSDependencyDetector;
import ca.concordia.cssanalyser.refactoring.dependencies.CSSValueOverridingDependencyList;
/**
* This class is the main class storing all CSS data in the memory
*
* @author Davood Mazinanian
*/
public class StyleSheet extends CSSModelObject {
private Map<Selector, Integer> selectors;
private String cssFilePath;
private CSSValueOverridingDependencyList orderDependencies;
public StyleSheet() {
selectors = new LinkedHashMap<>();
}
public void setPath(String path) {
cssFilePath = path;
}
/**
* Adds a new selector (whether single or grouped) to the selectors list of
* this style sheet.
*
* @param selector
*/
public void addSelector(Selector selector) {
if (!selectors.containsKey(selector)) {
selectors.put(selector, selectors.size() + 1);
}
selector.setParentStyleSheet(this);
}
/**
* Returns all the selectors, whether single or grouped in the style sheet.
*
* @return List<Selector>
*/
public Iterable<Selector> getAllSelectors() {
return selectors.keySet();
}
/**
* This method returns all the single selectors in the style sheet, in
* addition to the all single selectors inside the grouped selectors. It
* preserves the order of single selectors.
*
* @return List<BaseSelector>
*/
public List<BaseSelector> getAllBaseSelectors() {
List<BaseSelector> allBaseSelectors = new ArrayList<>();
for (Selector selector : selectors.keySet()) { // Look inside all selectors
if (selector instanceof BaseSelector) {
allBaseSelectors.add((BaseSelector) selector);
} else if (selector instanceof GroupingSelector) {
for (BaseSelector bs : ((GroupingSelector)selector).getBaseSelectors()) {
allBaseSelectors.add(bs);
}
}
}
return allBaseSelectors;
}
/**
* This method returns all the declarations inside a style sheet, preserving
* their order in which they have been defined.
*
* @return List<Declaration>
*/
public Set<Declaration> getAllDeclarations() {
//if (listOfDeclarations == null) {
Set<Declaration> listOfDeclarations = new LinkedHashSet<>();
for (Selector selector : selectors.keySet())
for (Declaration declaration : selector.getDeclarations())
listOfDeclarations.add(declaration);
//}
return listOfDeclarations;
}
@Override
public String toString() {
StringBuilder toReturn = new StringBuilder();
Set<MediaQueryList> lastMediaQueryLists = null;
Iterator<Selector> selectorIterator = selectors.keySet().iterator();
int currentIndentation = 0;
if (selectorIterator.hasNext()) {
Selector s = selectorIterator.next();
while(true) {
if (lastMediaQueryLists == null || (lastMediaQueryLists != null && !lastMediaQueryLists.equals(s.getMediaQueryLists()))) {
for (Iterator<MediaQueryList> mediaQueryList = s.getMediaQueryLists().iterator(); mediaQueryList.hasNext();) {
MediaQueryList mql = mediaQueryList.next();
if (lastMediaQueryLists == null || (lastMediaQueryLists != null && !lastMediaQueryLists.contains(mql))) {
toReturn.append(getIndentsString(currentIndentation));
toReturn.append(mql + " {" + System.lineSeparator() + System.lineSeparator()); // Open media query
currentIndentation++;
}
}
lastMediaQueryLists = s.getMediaQueryLists();
}
toReturn.append(getIndentsString(currentIndentation) + s + " {" + System.lineSeparator());
for (Declaration d : s.getDeclarations()) {
if (d instanceof ShorthandDeclaration) {
if (((ShorthandDeclaration)d).isVirtual())
continue;
}
toReturn.append(getIndentsString(currentIndentation + 1) + d);
if (d.isImportant())
toReturn.append(" !important");
toReturn.append(";" + System.lineSeparator());
}
toReturn.append(getIndentsString(currentIndentation) + "}" + System.lineSeparator() + System.lineSeparator());
if (selectorIterator.hasNext()) {
s = selectorIterator.next();
if (lastMediaQueryLists != null) {
if (!lastMediaQueryLists.equals(s.getMediaQueryLists())) {
// For each MediaQueryList which is not in the new selector, close the MediaQuery
for (MediaQueryList mq : lastMediaQueryLists) {
if (!s.getMediaQueryLists().contains(mq)) {
currentIndentation--;
toReturn.append(getIndentsString(currentIndentation) + "}" + System.lineSeparator() + System.lineSeparator()); // close media query
}
}
}
}
} else {
break;
}
}
while (currentIndentation > 0) { // unclosed media queries
currentIndentation--;
toReturn.append(getIndentsString(currentIndentation) + "}" + System.lineSeparator()); // close media query
}
}
return toReturn.toString();
}
private String getIndentsString(int currentIndentation) {
String s = "";
for (int i = 0; i < currentIndentation; i++)
s += "\t";
return s;
}
public void addSelectors(StyleSheet s) {
for (Selector selector : s.getAllSelectors())
addSelector(selector);
}
public String getFilePath() {
return cssFilePath;
}
@Override
public StyleSheet clone() {
StyleSheet styleSheet = new StyleSheet();
styleSheet.cssFilePath = cssFilePath;
for (Selector s : this.selectors.keySet())
styleSheet.addSelector(s.clone());
return styleSheet;
}
/**
* Maps every base selector to a node list in the stylesheet
* @param document
* @param styleSheet
* @return
*/
public Map<BaseSelector, DOMNodeWrapperList> mapStylesheetOnDocument(Document document) {
List<BaseSelector> allSelectors = getAllBaseSelectors();
Map<BaseSelector, DOMNodeWrapperList> selectorNodeListMap = new LinkedHashMap<>();
for (BaseSelector selector : allSelectors) {
selectorNodeListMap.put(selector, selector.getSelectedNodes(document));
}
return selectorNodeListMap;
}
/**
* Returns a list of documents' node in addition to the CSS selectors which
* select each node
* @param document
* @param styleSheet
* @return
*/
public Map<DOMNodeWrapper, List<BaseSelector>> getCSSClassesForDOMNodes(Document document) {
// Map every node in the DOM tree to a list of selectors in the stylesheet
Map<DOMNodeWrapper, List<BaseSelector>> nodeToSelectorsMapping = new HashMap<>();
for (BaseSelector selector : getAllBaseSelectors()) {
DOMNodeWrapperList matchedNodes = selector.getSelectedNodes(document);
for (DOMNodeWrapper domNodeWrapper : matchedNodes) {
List<BaseSelector> correspondingSelectors = nodeToSelectorsMapping.get(domNodeWrapper);
if (correspondingSelectors == null) {
correspondingSelectors = new ArrayList<>();
}
correspondingSelectors.add(selector);
nodeToSelectorsMapping.put(domNodeWrapper, correspondingSelectors);
}
}
return nodeToSelectorsMapping;
}
public void addMediaQueryList(MediaQueryList forMedia) {
for (Selector s : selectors.keySet())
s.addMediaQueryList(forMedia);
}
public CSSValueOverridingDependencyList getLastComputetOrderDependencies() {
return orderDependencies;
}
public CSSValueOverridingDependencyList getValueOverridingDependencies(Document dom) {
CSSDependencyDetector dependencyDetector;
if (dom != null) {
dependencyDetector = new CSSDependencyDetector(this, dom);
} else {
dependencyDetector = new CSSDependencyDetector(this);
}
orderDependencies = dependencyDetector.findOverridingDependancies();
return orderDependencies;
}
public CSSValueOverridingDependencyList getValueOverridingDependencies() {
return getValueOverridingDependencies(null);
}
public int getNumberOfSelectors() {
return selectors.size();
}
public void removeSelectors(List<Selector> selectorsToBeRemoved) {
for (Selector s : selectorsToBeRemoved)
selectors.remove(s);
// Update the numbers associated with every declaration
int i = 1;
for (Selector s : selectors.keySet())
selectors.put(s, i++);
}
public int getSelectorNumber(Selector selector) {
return selectors.get(selector);
}
public boolean containsSelector(Selector selector) {
return selectors.containsKey(selector);
}
public StyleSheet getStyleSheetWithIntraSelectorDependenciesRemoved() {
StyleSheet styleSheetToReturn = new StyleSheet();
styleSheetToReturn.cssFilePath = this.cssFilePath;
for (Selector selector : getAllSelectors()) {
Selector newSelector = selector.copyEmptySelector();
newSelector.setOriginalSelector(selector);
for (Declaration declaration : selector.getDeclarationsWithIntraSelectorDependenciesRemoved()) {
newSelector.addDeclaration(declaration);
styleSheetToReturn.addSelector(newSelector);
}
}
return styleSheetToReturn;
}
}