View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.devacfr.maven.skins.reflow.snippet;
20  
21  import javax.annotation.Nonnull;
22  
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  
27  import org.devacfr.maven.skins.reflow.ISkinConfig;
28  import org.devacfr.maven.skins.reflow.snippet.Processor.ShortcodeProcessor;
29  import org.devacfr.maven.skins.reflow.snippet.Processor.WebComponentProcessor;
30  import org.jsoup.Jsoup;
31  import org.jsoup.nodes.Document;
32  import org.jsoup.nodes.Element;
33  import org.jsoup.select.Elements;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import static java.util.Objects.requireNonNull;
38  
39  /**
40   * @author Christophe Friederich
41   * @version 2.4
42   */
43  public class SnippetParser {
44  
45      private static final Logger LOGGER = LoggerFactory.getLogger(SnippetParser.class);
46  
47      /** */
48      private final ComponentResolver resolver;
49  
50      /** */
51      private final ArrayList<ComponentToken> stack;
52  
53      /** */
54      private Iterator<Element> it;
55  
56      /** */
57      private Processor state = null;
58  
59      /** */
60      private final WebComponentProcessor webComponentProcessor;
61  
62      /** */
63      private final ShortcodeProcessor shortcodeProcessor;
64  
65      /** */
66      private final SnippetContext snippetContext;
67  
68      /** */
69      private ComponentToken currentToken;
70  
71      public SnippetParser() {
72          resolver = new ComponentResolver();
73          stack = new ArrayList<>(32);
74          snippetContext = new SnippetContext(this);
75          webComponentProcessor = new WebComponentProcessor(this);
76          shortcodeProcessor = new ShortcodeProcessor(this);
77      }
78  
79      public SnippetParser insertResourcePath(final String path, final int index) {
80          snippetContext.insertResourcePath(path, index);
81          return this;
82      }
83  
84      public SnippetParser addResourcePath(final String path) {
85          snippetContext.addResourcePath(path);
86          return this;
87      }
88  
89      public SnippetContext parse(@Nonnull final ISkinConfig config, @Nonnull final String htmlSource)
90              throws IOException {
91          requireNonNull(config);
92          requireNonNull(htmlSource);
93  
94          snippetContext.reset();
95          snippetContext.setConfig(config);
96  
97          if (LOGGER.isDebugEnabled()) {
98              LOGGER.debug("Parse Snippet");
99              LOGGER.debug(htmlSource);
100         }
101 
102         // find all snippets
103         final Document doc = resolver.normalize(Jsoup.parse(htmlSource));
104 
105         final Elements elements = resolver.collect(doc);
106 
107         for (it = elements.iterator(); it.hasNext();) {
108             try {
109                 parse();
110             } catch (final Exception ex) {
111                 throw new SnippetParseException(
112                         "error on parse token " + currentToken + " when generate file " + config.getFileId(), ex);
113             }
114         }
115         snippetContext.setHtmlSource(doc.html());
116         return snippetContext;
117     }
118 
119     protected void parse() {
120         if (!it.hasNext()) {
121             throw new SnippetParseException("EOF");
122         }
123         final Element element = it.next();
124         currentToken = resolver.create(element);
125         if (currentToken == null) {
126             throw new SnippetParseException("unknown component: " + element);
127         }
128         switch (currentToken.type()) {
129             case webComponent:
130                 state = webComponentProcessor;
131                 break;
132             case shortcode:
133                 state = shortcodeProcessor;
134                 break;
135             default:
136                 throw new SnippetParseException("unknown token type " + currentToken.type());
137         }
138         parse(currentToken);
139         currentToken = null;
140     }
141 
142     protected void parse(final ComponentToken token) {
143         state.parse(token);
144     }
145 
146     protected ComponentToken currentToken() {
147         final int size = stack.size();
148         return size > 0 ? stack.get(size - 1) : null;
149     }
150 
151     protected ComponentToken pop() {
152         final int size = stack.size();
153         if (LOGGER.isDebugEnabled()) {
154             LOGGER.debug("Stack size befor pop: {}", size);
155         }
156         final ComponentToken element = stack.remove(size - 1);
157         if (LOGGER.isDebugEnabled()) {
158             LOGGER.debug("Remove component from stack: {}", element);
159         }
160         return element;
161     }
162 
163     protected void push(final ComponentToken element) {
164         stack.add(element);
165         if (LOGGER.isDebugEnabled()) {
166             LOGGER.debug("Add component to stack: {}", element);
167         }
168     }
169 
170     protected SnippetContext getSnippetContext() {
171         return snippetContext;
172     }
173 }