1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.rendering.internal.parser.pygments

File PygmentsParser.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart9.png
41% of files have more coverage

Code metrics

10
39
4
1
219
115
14
0.36
9.75
4
3.5

Classes

Class Line # Actions
PygmentsParser 62 39 0% 14 9
0.830188783%
 

Contributing tests

This file is covered by 13 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.rendering.internal.parser.pygments;
21   
22    import java.io.IOException;
23    import java.io.InputStream;
24    import java.io.Reader;
25    import java.io.StringReader;
26    import java.util.Collections;
27    import java.util.List;
28   
29    import javax.inject.Inject;
30    import javax.inject.Named;
31    import javax.inject.Singleton;
32    import javax.script.ScriptContext;
33    import javax.script.ScriptEngine;
34    import javax.script.ScriptEngineManager;
35    import javax.script.ScriptException;
36    import javax.script.SimpleScriptContext;
37   
38    import org.apache.commons.io.IOUtils;
39    import org.xwiki.component.annotation.Component;
40    import org.xwiki.component.phase.Initializable;
41    import org.xwiki.component.phase.InitializationException;
42    import org.xwiki.rendering.block.Block;
43    import org.xwiki.rendering.block.NewLineBlock;
44    import org.xwiki.rendering.parser.AbstractHighlightParser;
45    import org.xwiki.rendering.parser.HighlightParser;
46    import org.xwiki.rendering.parser.ParseException;
47    import org.xwiki.rendering.parser.Parser;
48    import org.xwiki.rendering.syntax.Syntax;
49    import org.xwiki.rendering.syntax.SyntaxType;
50   
51    /**
52    * Highlight provided source using Pygments.
53    *
54    * @version $Id: 498b9839343c7b5f7c51523e71560e95cf21b4c6 $
55    * @since 1.7RC1
56    */
57    // Note that we force the Component annotation so that this component is only registered as a Highlight Parser
58    // and not a Parser too since we don't want this parser to be visible to users as a valid standard input parser
59    // component.
60    @Component(roles = {HighlightParser.class })
61    @Singleton
 
62    public class PygmentsParser extends AbstractHighlightParser implements Initializable
63    {
64    /**
65    * The name of the style variable in Python code.
66    */
67    private static final String PY_STYLE_VARNAME = "style";
68   
69    /**
70    * The name of the listener variable in Python code.
71    */
72    private static final String PY_LISTENER_VARNAME = "listener";
73   
74    /**
75    * The name of the variable containing the source code to highlight in Python code.
76    */
77    private static final String PY_CODE_VARNAME = "code";
78   
79    /**
80    * The name of the variable containing the language of the source.
81    */
82    private static final String PY_LANGUAGE_VARNAME = "language";
83   
84    /**
85    * The name of the lexer variable in Python code.
86    */
87    private static final String PY_LEXER_VARNAME = "pygmentLexer";
88   
89    /**
90    * The identifier of the Java Scripting engine to use.
91    */
92    private static final String ENGINE_ID = "python";
93   
94    /**
95    * The syntax identifier.
96    */
97    private Syntax syntax;
98   
99    /**
100    * Used to parse Pygment token values into blocks.
101    */
102    @Inject
103    @Named("plain/1.0")
104    private Parser plainTextParser;
105   
106    /**
107    * Pygments highligh parser configuration.
108    */
109    @Inject
110    private PygmentsParserConfiguration configuration;
111   
112    /**
113    * The JSR223 Script Engine we use to evaluate Python scripts.
114    */
115    private ScriptEngine engine;
116   
117    /**
118    * The Python script used to manipulate Pygments.
119    */
120    private String script;
121   
 
122  15 toggle @Override
123    public void initialize() throws InitializationException
124    {
125  15 ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
126   
127    // Get the script
128  15 InputStream is = getClass().getResourceAsStream("/pygments/code.py");
129  15 if (is != null) {
130  15 try {
131  15 this.script = IOUtils.toString(is, "UTF8");
132    } catch (Exception e) {
133  0 throw new InitializationException("Failed to read resource /pygments/code.py resource", e);
134    } finally {
135  15 IOUtils.closeQuietly(is);
136    }
137    } else {
138  0 throw new InitializationException("Failed to find resource /pygments/code.py resource");
139    }
140   
141    // Get the Python engine
142  15 this.engine = scriptEngineManager.getEngineByName(ENGINE_ID);
143   
144  15 if (this.engine == null) {
145  0 throw new InitializationException("Failed to find engine for Python script language");
146    }
147   
148  15 String highlightSyntaxId = getSyntaxId() + "-highlight";
149  15 this.syntax = new Syntax(new SyntaxType(highlightSyntaxId, highlightSyntaxId), "1.0");
150    }
151   
 
152  0 toggle @Override
153    public Syntax getSyntax()
154    {
155  0 return this.syntax;
156    }
157   
 
158  136 toggle @Override
159    public List<Block> highlight(String syntaxId, Reader source) throws ParseException
160    {
161  136 String code;
162  136 try {
163  136 code = IOUtils.toString(source);
164    } catch (IOException e) {
165  0 throw new ParseException("Failed to read source", e);
166    }
167   
168  136 if (code.length() == 0) {
169  1 return Collections.emptyList();
170    }
171   
172  135 List<Block> blocks;
173  135 try {
174  135 blocks = highlight(syntaxId, code);
175    } catch (ScriptException e) {
176  0 throw new ParseException("Failed to highlight code", e);
177    }
178   
179    // TODO: there is a bug in Pygments that makes it always put a newline at the end of the content
180  135 if (code.charAt(code.length() - 1) != '\n' && !blocks.isEmpty()
181    && blocks.get(blocks.size() - 1) instanceof NewLineBlock) {
182  132 blocks.remove(blocks.size() - 1);
183    }
184   
185  135 return blocks;
186    }
187   
188    /**
189    * Return a highlighted version of the provided content.
190    *
191    * @param syntaxId the identifier of the source syntax.
192    * @param code the content to highlight.
193    * @return the highlighted version of the provided source.
194    * @throws ScriptException when failed to execute the script
195    * @throws ParseException when failed to parse the content as plain text
196    */
 
197  135 toggle private List<Block> highlight(String syntaxId, String code) throws ScriptException, ParseException
198    {
199  135 BlocksGeneratorPygmentsListener listener = new BlocksGeneratorPygmentsListener(this.plainTextParser);
200   
201  135 ScriptContext scriptContext = new SimpleScriptContext();
202   
203  135 scriptContext.setAttribute(PY_LANGUAGE_VARNAME, syntaxId, ScriptContext.ENGINE_SCOPE);
204  135 scriptContext.setAttribute(PY_CODE_VARNAME, code, ScriptContext.ENGINE_SCOPE);
205  135 scriptContext.setAttribute(PY_STYLE_VARNAME, this.configuration.getStyle(), ScriptContext.ENGINE_SCOPE);
206  135 scriptContext.setAttribute(PY_LISTENER_VARNAME, listener, ScriptContext.ENGINE_SCOPE);
207   
208  135 this.engine.eval(this.script, scriptContext);
209   
210  135 List<Block> blocks;
211  135 if (scriptContext.getAttribute(PY_LEXER_VARNAME) != null) {
212  134 blocks = listener.getBlocks();
213    } else {
214  1 blocks = this.plainTextParser.parse(new StringReader(code)).getChildren().get(0).getChildren();
215    }
216   
217  135 return blocks;
218    }
219    }