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  import javax.annotation.Nullable;
23  
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  
28  import org.devacfr.maven.skins.reflow.ISkinConfig;
29  import org.devacfr.maven.skins.reflow.snippet.Processor.ShortcodeProcessor;
30  import org.devacfr.maven.skins.reflow.snippet.Processor.WebComponentProcessor;
31  import org.jsoup.Jsoup;
32  import org.jsoup.nodes.Document;
33  import org.jsoup.nodes.Element;
34  import org.jsoup.select.Elements;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import static java.util.Objects.requireNonNull;
39  
40  /**
41   * @author Christophe Friederich
42   * @version 2.4
43   */
44  public class SnippetParser {
45  
46      private static final Logger LOGGER = LoggerFactory.getLogger(SnippetParser.class);
47  
48      /** */
49      private final ComponentResolver resolver;
50  
51      /** */
52      private final ArrayList<ComponentToken> stack;
53  
54      /** */
55      private Iterator<Element> it;
56  
57      /** */
58      private Processor state = null;
59  
60      /** */
61      private final WebComponentProcessor webComponentProcessor;
62  
63      /** */
64      private final ShortcodeProcessor shortcodeProcessor;
65  
66      /** */
67      private final SnippetContext snippetContext;
68  
69      /** */
70      private ComponentToken currentToken;
71  
72      public SnippetParser() {
73          resolver = new ComponentResolver();
74          stack = new ArrayList<>(32);
75          snippetContext = new SnippetContext(this);
76          webComponentProcessor = new WebComponentProcessor(this);
77          shortcodeProcessor = new ShortcodeProcessor(this);
78      }
79  
80      public SnippetParser insertResourcePath(final String path, final int index) {
81          snippetContext.insertResourcePath(path, index);
82          return this;
83      }
84  
85      public SnippetParser addResourcePath(final String path) {
86          snippetContext.addResourcePath(path);
87          return this;
88      }
89  
90      public SnippetContext parse(@Nonnull final ISkinConfig config, @Nullable String htmlSource)
91              throws IOException {
92          requireNonNull(config);
93          if (htmlSource == null) {
94              htmlSource = "";
95          }
96  
97          snippetContext.reset();
98          snippetContext.setConfig(config);
99  
100         if (LOGGER.isDebugEnabled()) {
101             LOGGER.debug("Parse Snippet");
102             LOGGER.debug(htmlSource);
103         }
104 
105         // find all snippets
106         final Document doc = resolver.normalize(Jsoup.parse(htmlSource));
107 
108         final Elements elements = resolver.collect(doc);
109 
110         for (it = elements.iterator(); it.hasNext();) {
111             try {
112                 parse();
113             } catch (final Exception ex) {
114                 throw new SnippetParseException(
115                         "error on parse token " + currentToken + " when generate file " + config.getFileId(), ex);
116             }
117         }
118         snippetContext.setHtmlSource(doc.html());
119         return snippetContext;
120     }
121 
122     protected void parse() {
123         if (!it.hasNext()) {
124             throw new SnippetParseException("EOF");
125         }
126         final Element element = it.next();
127         currentToken = resolver.create(element);
128         if (currentToken == null) {
129             throw new SnippetParseException("unknown component: " + element);
130         }
131         switch (currentToken.type()) {
132             case webComponent:
133                 state = webComponentProcessor;
134                 break;
135             case shortcode:
136                 state = shortcodeProcessor;
137                 break;
138             default:
139                 throw new SnippetParseException("unknown token type " + currentToken.type());
140         }
141         parse(currentToken);
142         currentToken = null;
143     }
144 
145     protected void parse(final ComponentToken token) {
146         state.parse(token);
147     }
148 
149     protected ComponentToken currentToken() {
150         final int size = stack.size();
151         return size > 0 ? stack.get(size - 1) : null;
152     }
153 
154     protected ComponentToken pop() {
155         final int size = stack.size();
156         if (LOGGER.isDebugEnabled()) {
157             LOGGER.debug("Stack size befor pop: {}", size);
158         }
159         final ComponentToken element = stack.remove(size - 1);
160         if (LOGGER.isDebugEnabled()) {
161             LOGGER.debug("Remove component from stack: {}", element);
162         }
163         return element;
164     }
165 
166     protected void push(final ComponentToken element) {
167         stack.add(element);
168         if (LOGGER.isDebugEnabled()) {
169             LOGGER.debug("Add component to stack: {}", element);
170         }
171     }
172 
173     protected SnippetContext getSnippetContext() {
174         return snippetContext;
175     }
176 }