View Javadoc
1   /**
2    * This file Copyright (c) 2011-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.vaadin.gwt.client.editor.dom.processor;
35  
36  import info.magnolia.ui.vaadin.gwt.client.editor.dom.Comment;
37  import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlArea;
38  import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlComponent;
39  import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlElement;
40  import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlPage;
41  import info.magnolia.ui.vaadin.gwt.client.editor.model.Model;
42  import info.magnolia.ui.vaadin.gwt.client.shared.ErrorType;
43  
44  import java.util.HashMap;
45  import java.util.Map;
46  
47  import com.google.gwt.core.client.GWT;
48  import com.google.gwt.dom.client.BodyElement;
49  import com.google.gwt.dom.client.Element;
50  import com.google.gwt.dom.client.HeadElement;
51  import com.google.gwt.dom.client.Node;
52  import com.google.gwt.event.shared.EventBus;
53  import com.google.gwt.regexp.shared.MatchResult;
54  import com.google.gwt.regexp.shared.RegExp;
55  
56  /**
57   * Processor for comment elements. This processor builds a {@link info.magnolia.ui.vaadin.gwt.client.editor.dom.CmsNode}-tree
58   * based on {@link Comment} elements in the DOM structure. <br />
59   * The nesting of elements is given by the opening and closing of the {@link Comment} tags wrapped by {@link CMSComment}s.
60   * In case it processes a starting comment-tag a new {@link MgnlElement} is created and returned.
61   * In case of a closing comment-tag it will return the parent.
62   */
63  public class CommentProcessor {
64  
65      public MgnlElementadin/gwt/client/editor/dom/MgnlElement.html#MgnlElement">MgnlElement process(Model model, EventBus eventBus, Node node, MgnlElement currentElement) throws ProcessException {
66  
67          CMSComment comment = getCmsComment(node);
68  
69          // in case we fail, we want to keep the currentElement as current.
70          MgnlElement mgnlElement = currentElement;
71  
72          if (!comment.isClosing()) {
73              // just validate the open comment of area and component
74              validateAreaAndComponentComments(node, comment);
75  
76              mgnlElement = createMgnlElement(comment, currentElement, eventBus);
77              mgnlElement.setStartComment((Element) node.cast());
78  
79              // validate and set page element
80              if (mgnlElement.getParent() == null) {
81                  if (!(mgnlElement instanceof MgnlPage)) {
82                      throw new ProcessException(ErrorType.EXPECTED_PAGE_TAG, comment.getTagName());
83                  }
84                  model.setRootPage((MgnlPage) mgnlElement);
85  
86              } else {
87                  // validate and add area and component elements
88                  if (mgnlElement.getParent().asMgnlElement().isPage()) {
89                      if (!(mgnlElement instanceof MgnlArea)) {
90                          throw new ProcessException(ErrorType.EXPECTED_AREA_TAG, comment.getTagName());
91                      }
92                      model.addRootArea((MgnlArea) mgnlElement);
93                  }
94                  mgnlElement.getParent().getChildren().add(mgnlElement);
95              }
96          }
97          // the cms:page tag should span throughout the page, but doesn't: kind of a hack.
98          else if (currentElement != null && !currentElement.isPage()) {
99              currentElement.setEndComment((Element) node.cast());
100             mgnlElement = currentElement.getParent().asMgnlElement();
101         }
102 
103         return mgnlElement;
104 
105     }
106 
107     private CMSComment getCmsComment(Node node) throws IllegalArgumentException {
108 
109         CMSComment cmsComment = new CMSComment();
110 
111         Comment domComment = node.cast();
112         String comment = domComment.getData().trim();
113 
114         GWT.log("processing comment " + comment);
115 
116         String tagName = "";
117         boolean isClosing = false;
118 
119         int delimiter = comment.indexOf(" ");
120         String attributeString = "";
121 
122         if (delimiter < 0) {
123             tagName = comment;
124         } else {
125             tagName = comment.substring(0, delimiter);
126             attributeString = comment.substring(delimiter + 1);
127         }
128 
129         if (tagName.startsWith("/")) {
130             isClosing = true;
131             tagName = tagName.substring(1);
132         }
133 
134         if (tagName.startsWith(Model.CMS_TAG)) {
135             cmsComment.setTagName(tagName);
136             cmsComment.setAttribute(attributeString);
137             cmsComment.setClosing(isClosing);
138 
139         } else {
140             throw new IllegalArgumentException("Tagname must start with +'" + Model.CMS_TAG + "'.");
141         }
142         return cmsComment;
143     }
144 
145     /**
146      * Check if the given <code>cms.area, cms.component</code> is in <code>head/body</code> tag or not.
147      */
148     private void validateAreaAndComponentComments(Node node, CMSComment comment) throws ProcessException {
149         String tagName = comment.getTagName();
150         boolean isAreaOrComponentTag = tagName.startsWith(Model.CMS_AREA) || tagName.startsWith(Model.CMS_COMPONENT);
151         BodyElement body = BodyElement.as(node.getOwnerDocument().getElementsByTagName("body").getItem(0));
152         HeadElement head = HeadElement.as(node.getOwnerDocument().getElementsByTagName("head").getItem(0));
153         if (isAreaOrComponentTag && !(head.isOrHasChild(node) || body.isOrHasChild(node))) {
154             throw new ProcessException(ErrorType.TAG_OUTSIDE_DOCUMENT, tagName);
155         }
156     }
157 
158     /**
159      * Creates an attributes map by parsing the comment string for all relevant data.
160      * Overrides or adds attributes defined in {@link Model#INHERITED_ATTRIBUTES} from the parent element.
161      * @param attributeString a string containing all data associated with a {@link CMSComment}
162      * @param parent the parent element
163      */
164     private Map<String, String> getAttributes(String attributeString, MgnlElement parent) {
165         String[] keyValue;
166         Map<String, String> attributes = new HashMap<String, String>();
167 
168         RegExp regExp = RegExp.compile("(\\S+=[\"'][^\"]*[\"'])", "g");
169         for (MatchResult matcher = regExp.exec(attributeString); matcher != null; matcher = regExp.exec(attributeString)) {
170             keyValue = matcher.getGroup(0).split("=");
171             if (keyValue[0].equals("content")) {
172                 String content = keyValue[1].replace("\"", "");
173                 int i = content.indexOf(':');
174                 attributes.put("workspace", content.substring(0, i));
175                 attributes.put("path", content.substring(i + 1));
176             } else {
177                 attributes.put(keyValue[0], keyValue[1].replace("\"", ""));
178             }
179         }
180         if (parent != null) {
181             for (String inheritedAttribute : Model.INHERITED_ATTRIBUTES) {
182                 if (parent.containsAttribute(inheritedAttribute)) {
183                     attributes.put(inheritedAttribute, parent.getAttribute(inheritedAttribute));
184                 }
185             }
186         }
187         return attributes;
188     }
189 
190     private MgnlElementagnolia/ui/vaadin/gwt/client/editor/dom/MgnlElement.html#MgnlElement">MgnlElement createMgnlElement(CMSComment comment, MgnlElement parent, EventBus eventBus) throws IllegalArgumentException {
191         String tagName = comment.getTagName();
192         MgnlElement mgnlElement;
193         if (Model.CMS_PAGE.equals(tagName)) {
194             mgnlElement = new MgnlPage(parent);
195         } else if (Model.CMS_AREA.equals(tagName)) {
196             mgnlElement = new MgnlArea(parent, eventBus);
197         } else if (Model.CMS_COMPONENT.equals(tagName)) {
198             mgnlElement = new MgnlComponent(parent, eventBus);
199         } else {
200             throw new IllegalArgumentException("The tagname must be one of the defined marker Strings.");
201         }
202 
203         mgnlElement.setAttributes(getAttributes(comment.getAttributes(), parent));
204 
205         return mgnlElement;
206     }
207 
208     /**
209      * Wrapper element for {@link Comment}s.
210      */
211     private class CMSComment {
212 
213         private String tagName;
214         private String attributes;
215         private boolean isClosing = false;
216 
217         public String getTagName() {
218             return tagName;
219         }
220 
221         public void setTagName(String tagName) {
222             this.tagName = tagName;
223         }
224 
225         public boolean isClosing() {
226             return isClosing;
227         }
228 
229         public void setClosing(boolean isClosing) {
230             this.isClosing = isClosing;
231         }
232 
233         public void setAttribute(String attributes) {
234             this.attributes = attributes;
235         }
236 
237         public String getAttributes() {
238             return attributes;
239         }
240     }
241 }