001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.ui.graphicGraph.stylesheet; 033 034import java.awt.Color; 035import java.io.BufferedInputStream; 036import java.io.File; 037import java.io.FileInputStream; 038import java.io.IOException; 039import java.io.InputStreamReader; 040import java.io.Reader; 041import java.io.StringReader; 042import java.net.URL; 043import java.util.ArrayList; 044import java.util.HashMap; 045 046import org.graphstream.graph.Edge; 047import org.graphstream.graph.Element; 048import org.graphstream.graph.Graph; 049import org.graphstream.graph.Node; 050import org.graphstream.ui.graphicGraph.GraphicSprite; 051import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.StrokeMode; 052import org.graphstream.ui.graphicGraph.stylesheet.parser.StyleSheetParser; 053import org.graphstream.util.parser.ParseException; 054 055/** 056 * Implementation of the style sheets that can be stored in the graphic graph. 057 * 058 * @author Antoine Dutot 059 */ 060public class StyleSheet { 061 // Attributes 062 063 /** 064 * The top-level default rule. 065 */ 066 public Rule defaultRule; 067 068 /** 069 * The default, id and class rules for graphs. 070 */ 071 public NameSpace graphRules = new NameSpace(Selector.Type.GRAPH); 072 073 /** 074 * The default, id and class rules for nodes. 075 */ 076 public NameSpace nodeRules = new NameSpace(Selector.Type.NODE); 077 078 /** 079 * The default, id and class rules for edges. 080 */ 081 public NameSpace edgeRules = new NameSpace(Selector.Type.EDGE); 082 083 /** 084 * The default, id and class rules for sprites. 085 */ 086 public NameSpace spriteRules = new NameSpace(Selector.Type.SPRITE); 087 088 /** 089 * Set of listeners. 090 */ 091 public ArrayList<StyleSheetListener> listeners = new ArrayList<StyleSheetListener>(); 092 093 // Constructors 094 095 /** 096 * New style sheet initialised to defaults. 097 */ 098 public StyleSheet() { 099 initRules(); 100 } 101 102 // Access 103 104 /** 105 * The default rule for graphs. 106 * 107 * @return A rule. 108 */ 109 public Rule getDefaultGraphRule() { 110 return graphRules.defaultRule; 111 } 112 113 /** 114 * The default rule for nodes. 115 * 116 * @return A rule. 117 */ 118 public Rule getDefaultNodeRule() { 119 return nodeRules.defaultRule; 120 } 121 122 /** 123 * The default rule for edges. 124 * 125 * @return A rule. 126 */ 127 public Rule getDefaultEdgeRule() { 128 return edgeRules.defaultRule; 129 } 130 131 /** 132 * The default rule for sprites. 133 * 134 * @return A rule. 135 */ 136 public Rule getDefaultSpriteRule() { 137 return spriteRules.defaultRule; 138 } 139 140 /** 141 * The default style for graphs. 142 * 143 * @return A style. 144 */ 145 public Style getDefaultGraphStyle() { 146 return getDefaultGraphRule().getStyle(); 147 } 148 149 /** 150 * The default style for nodes. 151 * 152 * @return A style. 153 */ 154 public Style getDefaultNodeStyle() { 155 return getDefaultNodeRule().getStyle(); 156 } 157 158 /** 159 * The default style for edges. 160 * 161 * @return A style. 162 */ 163 public Style getDefaultEdgeStyle() { 164 return getDefaultEdgeRule().getStyle(); 165 } 166 167 /** 168 * The default style for sprites. 169 * 170 * @return A style. 171 */ 172 public Style getDefaultSpriteStyle() { 173 return getDefaultSpriteRule().getStyle(); 174 } 175 176 /** 177 * All the rules (default, specific and class) that apply to graphs. 178 * 179 * @return The set of rules for graphs. 180 */ 181 public NameSpace getGraphStyleNameSpace() { 182 return graphRules; 183 } 184 185 /** 186 * All the rules (default, specific and class) that apply to nodes. 187 * 188 * @return The set of rules for nodes. 189 */ 190 public NameSpace getNodeStyleNameSpace() { 191 return nodeRules; 192 } 193 194 /** 195 * All the rules (default, specific and class) that apply to edges. 196 * 197 * @return The set of rules for edges. 198 */ 199 public NameSpace getEdgeStyleNameSpace() { 200 return edgeRules; 201 } 202 203 /** 204 * All the rules (default, specific and class) that apply to sprites. 205 * 206 * @return The set of rules for sprites. 207 */ 208 public NameSpace getSpriteStyleNameSpace() { 209 return spriteRules; 210 } 211 212 /** 213 * Get the rules that match a given element. 214 * 215 * First a rule for the identifier of the element is looked for. It is 216 * looked for in its name space (nodes for Node element, etc.) If it is not 217 * found, the default rule for this kind of element is used. This rule is 218 * pushed at start of the returned array of rules. 219 * 220 * After a rule for the element is found, then the various classes the 221 * element pertains to are looked at and each class rule found is added in 222 * order in the returned array. 223 * 224 * @param element 225 * The element a rules are searched for. 226 * @return A set of rules matching the element, with the main rule at index 227 * 0. 228 */ 229 public ArrayList<Rule> getRulesFor(Element element) { 230 ArrayList<Rule> rules = null; 231 232 if (element instanceof Graph) { 233 rules = graphRules.getRulesFor(element); 234 } else if (element instanceof Node) { 235 rules = nodeRules.getRulesFor(element); 236 } else if (element instanceof Edge) { 237 rules = edgeRules.getRulesFor(element); 238 } else if (element instanceof GraphicSprite) { 239 rules = spriteRules.getRulesFor(element); 240 } else { 241 rules = new ArrayList<Rule>(); 242 rules.add(defaultRule); 243 } 244 245 return rules; 246 } 247 248 /** 249 * Compute the name of the style group and element will pertain to knowing 250 * its styling rules. 251 * 252 * @param element 253 * The element. 254 * @param rules 255 * The styling rules. 256 * @return The unique identifier of the style group for the element. 257 * @see #getRulesFor(Element) 258 */ 259 public String getStyleGroupIdFor(Element element, ArrayList<Rule> rules) { 260 StringBuilder builder = new StringBuilder(); 261 262 if (element instanceof Graph) { 263 builder.append("g"); 264 } else if (element instanceof Node) { 265 builder.append("n"); 266 } else if (element instanceof Edge) { 267 builder.append("e"); 268 } else if (element instanceof GraphicSprite) { 269 builder.append("s"); 270 } else { 271 throw new RuntimeException("What ?"); 272 } 273 274 if (rules.get(0).selector.getId() != null) { 275 builder.append('_'); 276 builder.append(rules.get(0).selector.getId()); 277 } 278 279 int n = rules.size(); 280 281 if (n > 1) { 282 builder.append('('); 283 builder.append(rules.get(1).selector.getClazz()); 284 for (int i = 2; i < n; i++) { 285 builder.append(','); 286 builder.append(rules.get(i).selector.getClazz()); 287 } 288 builder.append(')'); 289 } 290 291 return builder.toString(); 292 } 293 294 // Commands 295 296 /** 297 * Create the default rules. This method is the place to set defaults for 298 * specific element types. This is here that the edge width is reset to one, 299 * since the default width is larger. The default z index that is different 300 * for every class of element is also set here. 301 */ 302 protected void initRules() { 303 defaultRule = new Rule(new Selector(Selector.Type.ANY), null); 304 305 defaultRule.getStyle().setDefaults(); 306 307 graphRules.defaultRule = new Rule(new Selector(Selector.Type.GRAPH), 308 defaultRule); 309 nodeRules.defaultRule = new Rule(new Selector(Selector.Type.NODE), 310 defaultRule); 311 edgeRules.defaultRule = new Rule(new Selector(Selector.Type.EDGE), 312 defaultRule); 313 spriteRules.defaultRule = new Rule(new Selector(Selector.Type.SPRITE), 314 defaultRule); 315 316 graphRules.defaultRule.getStyle().setValue("padding", 317 new Values(Style.Units.PX, 30)); 318 edgeRules.defaultRule.getStyle().setValue("shape", 319 StyleConstants.Shape.LINE); 320 edgeRules.defaultRule.getStyle().setValue("size", 321 new Values(Style.Units.PX, 1)); 322 edgeRules.defaultRule.getStyle().setValue("z-index", new Integer(1)); 323 nodeRules.defaultRule.getStyle().setValue("z-index", new Integer(2)); 324 spriteRules.defaultRule.getStyle().setValue("z-index", new Integer(3)); 325 326 Colors colors = new Colors(); 327 colors.add(Color.WHITE); 328 329 graphRules.defaultRule.getStyle().setValue("fill-color", colors); 330 graphRules.defaultRule.getStyle().setValue("stroke-mode", 331 StrokeMode.NONE); 332 333 for (StyleSheetListener listener : listeners) { 334 listener.styleAdded(defaultRule, defaultRule); 335 listener.styleAdded(graphRules.defaultRule, graphRules.defaultRule); 336 listener.styleAdded(nodeRules.defaultRule, nodeRules.defaultRule); 337 listener.styleAdded(edgeRules.defaultRule, edgeRules.defaultRule); 338 listener.styleAdded(spriteRules.defaultRule, 339 spriteRules.defaultRule); 340 } 341 342 // for( StyleSheetListener listener: listeners ) 343 // listener.styleAdded( defaultRule, defaultRule ); 344 // for( StyleSheetListener listener: listeners ) 345 // listener.styleAdded( graphRules.defaultRule, graphRules.defaultRule 346 // ); 347 // for( StyleSheetListener listener: listeners ) 348 // listener.styleAdded( nodeRules.defaultRule, nodeRules.defaultRule ); 349 // for( StyleSheetListener listener: listeners ) 350 // listener.styleAdded( edgeRules.defaultRule, edgeRules.defaultRule ); 351 // for( StyleSheetListener listener: listeners ) 352 // listener.styleAdded( spriteRules.defaultRule, spriteRules.defaultRule 353 // ); 354 } 355 356 /** 357 * Add a listener for style events. You never receive events for default 358 * rules and styles. You receive events only for the rules and styles that 359 * are added after this listener is registered. 360 * 361 * @param listener 362 * The new listener. 363 */ 364 public void addListener(StyleSheetListener listener) { 365 listeners.add(listener); 366 } 367 368 /** 369 * Remove a previously registered listener. 370 * 371 * @param listener 372 * The listener to remove. 373 */ 374 public void removeListener(StyleSheetListener listener) { 375 int index = listeners.indexOf(listener); 376 377 if (index >= 0) 378 listeners.remove(index); 379 } 380 381 /** 382 * Clear all specific rules and initialise the default rules. The listeners 383 * are not changed. 384 */ 385 public void clear() { 386 graphRules.clear(); 387 nodeRules.clear(); 388 edgeRules.clear(); 389 spriteRules.clear(); 390 initRules(); 391 392 for (StyleSheetListener listener : listeners) 393 listener.styleSheetCleared(); 394 } 395 396 /** 397 * Parse a style sheet from a file. The style sheet will complete the 398 * previously parsed style sheets. 399 * 400 * @param fileName 401 * Name of the file containing the style sheet. 402 * @throws IOException 403 * For any kind of I/O error or parse error. 404 */ 405 public void parseFromFile(String fileName) throws IOException { 406 parse(new InputStreamReader(new BufferedInputStream( 407 new FileInputStream(fileName)))); 408 } 409 410 /** 411 * Parse a style sheet from an URL. The style sheet will complete the 412 * previously parsed style sheets. First, this method will search the URL as 413 * SystemRessource, then as a file and if there is no match, just try to 414 * create an URL object giving the URL as constructor's parameter. 415 * 416 * @param url 417 * Name of the file containing the style sheet. 418 * @throws IOException 419 * For any kind of I/O error or parse error. 420 */ 421 public void parseFromURL(String url) throws IOException { 422 URL u = StyleSheet.class.getClassLoader().getResource(url); 423 if (u == null) { 424 File f = new File(url); 425 426 if (f.exists()) 427 u = f.toURI().toURL(); 428 else 429 u = new URL(url); 430 } 431 432 parse(new InputStreamReader(u.openStream())); 433 } 434 435 /** 436 * Parse a style sheet from a string. The style sheet will complete the 437 * previously parsed style sheets. 438 * 439 * @param styleSheet 440 * The string containing the whole style sheet. 441 * @throws IOException 442 * For any kind of I/O error or parse error. 443 */ 444 public void parseFromString(String styleSheet) throws IOException { 445 parse(new StringReader(styleSheet)); 446 } 447 448 /** 449 * Parse only one style, create a rule with the given selector, and add this 450 * rule. 451 * 452 * @param select 453 * The elements for which this style must apply. 454 * @param styleString 455 * The style string to parse. 456 */ 457 public void parseStyleFromString(Selector select, String styleString) 458 throws IOException { 459 StyleSheetParser parser = new StyleSheetParser(this, new StringReader( 460 styleString)); 461 462 Style style = new Style(); 463 464 try { 465 parser.stylesStart(style); 466 } catch (ParseException e) { 467 throw new IOException(e.getMessage()); 468 } 469 470 Rule rule = new Rule(select); 471 472 rule.setStyle(style); 473 addRule(rule); 474 } 475 476 /** 477 * Load a style sheet from an attribute value, the value can either be the 478 * contents of the whole style sheet, or begin by "url". If it starts with 479 * "url", it must then contain between parenthesis the string of the URL to 480 * load. For example: 481 * 482 * <pre> 483 * url('file:///some/path/on/the/file/system') 484 * </pre> 485 * 486 * Or 487 * 488 * <pre> 489 * url('http://some/web/url') 490 * </pre> 491 * 492 * The loaded style sheet will be merged with the styles already present in 493 * the style sheet. 494 * 495 * @param styleSheetValue 496 * The style sheet name of content. 497 * @throws IOException 498 * If the loading or parsing of the style sheet failed. 499 */ 500 public void load(String styleSheetValue) throws IOException { 501 if (styleSheetValue.startsWith("url")) { 502 // Extract the part between '(' and ')'. 503 504 int beg = styleSheetValue.indexOf('('); 505 int end = styleSheetValue.lastIndexOf(')'); 506 507 if (beg >= 0 && end > beg) 508 styleSheetValue = styleSheetValue.substring(beg + 1, end); 509 510 styleSheetValue = styleSheetValue.trim(); 511 512 // Remove the quotes (') or ("). 513 514 if (styleSheetValue.startsWith("'")) { 515 beg = 0; 516 end = styleSheetValue.lastIndexOf('\''); 517 518 if (beg >= 0 && end > beg) 519 styleSheetValue = styleSheetValue.substring(beg + 1, end); 520 } 521 522 styleSheetValue = styleSheetValue.trim(); 523 524 if (styleSheetValue.startsWith("\"")) { 525 beg = 0; 526 end = styleSheetValue.lastIndexOf('"'); 527 528 if (beg >= 0 && end > beg) 529 styleSheetValue = styleSheetValue.substring(beg + 1, end); 530 } 531 532 // That's it. 533 534 parseFromURL(styleSheetValue); 535 } else // Parse from string, the value is considered to be the style 536 // sheet contents. 537 { 538 parseFromString(styleSheetValue); 539 } 540 } 541 542 /** 543 * Parse the style sheet from the given reader. 544 * 545 * @param reader 546 * The reader pointing at the style sheet. 547 * @throws IOException 548 * For any kind of I/O error or parse error. 549 */ 550 protected void parse(Reader reader) throws IOException { 551 StyleSheetParser parser = new StyleSheetParser(this, reader); 552 553 try { 554 parser.start(); 555 } catch (ParseException e) { 556 throw new IOException(e.getMessage()); 557 } 558 } 559 560 /** 561 * Add a new rule with its style. If the rule selector is just GRAPH, NODE, 562 * EDGE or SPRITE, the default corresponding rules make a copy (or 563 * augmentation) of its style. Else if an id or class is specified the rules 564 * are added (or changed/augmented if the id or class was already set) and 565 * their parent is set to the default graph, node, edge or sprite rules. If 566 * this is an event rule (or meta-class rule), its sibling rule (the same 567 * rule without the meta-class) is searched and created if not found and the 568 * event rule is added as an alternative to it. 569 * 570 * @param newRule 571 * The new rule. 572 */ 573 public void addRule(Rule newRule) { 574 Rule oldRule = null; 575 576 switch (newRule.selector.getType()) { 577 case ANY: 578 throw new RuntimeException( 579 "The ANY selector should never be used, it is created automatically."); 580 case GRAPH: 581 oldRule = graphRules.addRule(newRule); 582 break; 583 case NODE: 584 oldRule = nodeRules.addRule(newRule); 585 break; 586 case EDGE: 587 oldRule = edgeRules.addRule(newRule); 588 break; 589 case SPRITE: 590 oldRule = spriteRules.addRule(newRule); 591 break; 592 default: 593 throw new RuntimeException("Ho ho ho ?"); 594 } 595 596 for (StyleSheetListener listener : listeners) 597 listener.styleAdded(oldRule, newRule); 598 } 599 600 @Override 601 public String toString() { 602 StringBuilder builder = new StringBuilder(); 603 604 builder.append("StyleSheet : {\n"); 605 builder.append(" default styles:\n"); 606 builder.append(defaultRule.toString(1)); 607 builder.append(graphRules.toString(1)); 608 builder.append(nodeRules.toString(1)); 609 builder.append(edgeRules.toString(1)); 610 builder.append(spriteRules.toString(1)); 611 612 return builder.toString(); 613 } 614 615 // Nested classes 616 617 /** 618 * A name space is a tuple (default rule, id rule set, class rule set). 619 * 620 * <p> 621 * The name space defines a default rule for a kind of elements, a set of 622 * rules for this kind of elements with a given identifier, and a set or 623 * rules for this kind of elements with a given class. 624 * </p> 625 */ 626 public class NameSpace { 627 // Attribute 628 629 /** 630 * The kind of elements in this name space. 631 */ 632 public Selector.Type type; 633 634 /** 635 * The default rule for this kind of elements. 636 */ 637 public Rule defaultRule; 638 639 /** 640 * The set of rules for this kind of elements with a given identifier. 641 */ 642 public HashMap<String, Rule> byId = new HashMap<String, Rule>(); 643 644 /** 645 * The set of rules for this kind of elements with a given class. 646 */ 647 public HashMap<String, Rule> byClass = new HashMap<String, Rule>(); 648 649 // Constructor 650 651 public NameSpace(Selector.Type type) { 652 this.type = type; 653 } 654 655 // Access 656 657 /** 658 * The kind of elements this name space applies rules to. 659 * 660 * @return A type of element (node, edge, sprite, graph). 661 */ 662 public Selector.Type getGraphElementType() { 663 return type; 664 } 665 666 /** 667 * Number of specific (id) rules. 668 * 669 * @return The number of rules that apply to elements by their 670 * identifiers. 671 */ 672 public int getIdRulesCount() { 673 return byId.size(); 674 } 675 676 /** 677 * Number of specific (class) rules. 678 * 679 * @return The number of rules that apply to elements by their classes. 680 */ 681 public int getClassRulesCount() { 682 return byClass.size(); 683 } 684 685 /** 686 * Get the rules that match a given element. The rules are returned in a 687 * given order. The array always contain the "main" rule that matches 688 * the element. This rule is either a default rule for the kind of 689 * element given or the rule that matches its identifier if there is 690 * one. Then class rules the element has can be appended to this array 691 * in order. 692 * 693 * @return an array of rules that match the element, with the main rule 694 * at index 0. 695 */ 696 protected ArrayList<Rule> getRulesFor(Element element) { 697 Rule rule = byId.get(element.getId()); 698 ArrayList<Rule> rules = new ArrayList<Rule>(); 699 700 if (rule != null) 701 rules.add(rule); 702 else 703 rules.add(defaultRule); 704 705 getClassRules(element, rules); 706 707 if (rules.isEmpty()) 708 rules.add(defaultRule); 709 710 return rules; 711 } 712 713 /** 714 * Search if the given element has classes attributes and fill the given 715 * array with the set of rules that match these classes. 716 * 717 * @param element 718 * The element for which classes must be found. 719 * @param rules 720 * The rule array to fill. 721 */ 722 protected void getClassRules(Element element, ArrayList<Rule> rules) { 723 Object o = element.getAttribute("ui.class"); 724 725 if (o != null) { 726 if (o instanceof Object[]) { 727 for (Object s : (Object[]) o) { 728 if (s instanceof CharSequence) { 729 Rule rule = byClass.get((CharSequence) s); 730 731 if (rule != null) 732 rules.add(rule); 733 } 734 } 735 } else if (o instanceof CharSequence) { 736 String classList = ((CharSequence) o).toString().trim(); 737 String[] classes = classList.split("\\s*,\\s*"); 738 739 for (String c : classes) { 740 Rule rule = byClass.get(c); 741 742 if (rule != null) 743 rules.add(rule); 744 } 745 } else { 746 throw new RuntimeException( 747 "Oups ! class attribute is of type " 748 + o.getClass().getName()); 749 } 750 } 751 } 752 753 // Command 754 755 /** 756 * Remove all styles. 757 */ 758 protected void clear() { 759 defaultRule = null; 760 byId.clear(); 761 byClass.clear(); 762 } 763 764 /** 765 * Add a new rule. 766 * 767 * <p> 768 * Several cases can occur : 769 * </p> 770 * 771 * <ul> 772 * <li>The rule to add has an ID or class and the rule does not yet 773 * exists and is not an event rule : add it directly.</li> 774 * <li>If the rule has an ID or class but the rule already exists, 775 * augment to already existing rule.</li> 776 * <li>If the rule has no ID or class and is not an event, augment the 777 * default style.</li> 778 * <li>If the rule is an event, the corresponding normal rule is 779 * searched, if it does not exists, it is created then or else, the 780 * event is added to the found rule.</li> 781 * </ul> 782 * 783 * @param newRule 784 * The rule to add or copy. 785 * @return It the rule added augments an existing rule, this existing 786 * rule is returned, else null is returned. 787 */ 788 protected Rule addRule(Rule newRule) { 789 Rule oldRule = null; 790 791 if (newRule.selector.getPseudoClass() != null) { 792 oldRule = addEventRule(newRule); 793 } else if (newRule.selector.getId() != null) { 794 oldRule = byId.get(newRule.selector.getId()); 795 796 if (oldRule != null) { 797 oldRule.getStyle().augment(newRule.getStyle()); 798 } else { 799 byId.put(newRule.selector.getId(), newRule); 800 newRule.getStyle().reparent(defaultRule); 801 } 802 } else if (newRule.selector.getClazz() != null) { 803 oldRule = byClass.get(newRule.selector.getClazz()); 804 805 if (oldRule != null) { 806 oldRule.getStyle().augment(newRule.getStyle()); 807 } else { 808 byClass.put(newRule.selector.getClazz(), newRule); 809 newRule.getStyle().reparent(defaultRule); 810 } 811 } else { 812 oldRule = defaultRule; 813 defaultRule.getStyle().augment(newRule.getStyle()); 814 newRule = defaultRule; 815 } 816 817 // That's it. 818 819 return oldRule; 820 } 821 822 protected Rule addEventRule(Rule newRule) { 823 Rule parentRule = null; 824 825 if (newRule.selector.getId() != null) { 826 parentRule = byId.get(newRule.selector.getId()); 827 828 if (parentRule == null) { 829 parentRule = addRule(new Rule(new Selector( 830 newRule.selector.getType(), 831 newRule.selector.getId(), 832 newRule.selector.getClazz()))); 833 } 834 } else if (newRule.selector.getClazz() != null) { 835 parentRule = byClass.get(newRule.selector.getClazz()); 836 837 if (parentRule == null) { 838 parentRule = addRule(new Rule(new Selector( 839 newRule.selector.getType(), 840 newRule.selector.getId(), 841 newRule.selector.getClazz()))); 842 } 843 } else { 844 parentRule = defaultRule; 845 } 846 847 newRule.getStyle().reparent(parentRule); 848 parentRule.getStyle().addAlternateStyle( 849 newRule.selector.getPseudoClass(), newRule); 850 851 return parentRule; 852 } 853 854 @Override 855 public String toString() { 856 return toString(-1); 857 } 858 859 public String toString(int level) { 860 String prefix = ""; 861 862 if (level > 0) { 863 for (int i = 0; i < level; i++) 864 prefix += " "; 865 } 866 867 StringBuilder builder = new StringBuilder(); 868 869 builder.append(String 870 .format("%s%s default style :%n", prefix, type)); 871 builder.append(defaultRule.toString(level + 1)); 872 toStringRules(level, builder, byId, 873 String.format("%s%s id styles", prefix, type)); 874 toStringRules(level, builder, byClass, 875 String.format("%s%s class styles", prefix, type)); 876 877 return builder.toString(); 878 } 879 880 protected void toStringRules(int level, StringBuilder builder, 881 HashMap<String, Rule> rules, String title) { 882 builder.append(title); 883 builder.append(String.format(" :%n")); 884 885 for (Rule rule : rules.values()) 886 builder.append(rule.toString(level + 1)); 887 } 888 } 889}