1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.rendering.internal.parser.pygments

File PygmentsParser.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart9.png
38% 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 12 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  12 toggle @Override
123    public void initialize() throws InitializationException
124    {
125  12 ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
126   
127    // Get the script
128  12 InputStream is = getClass().getResourceAsStream("/pygments/code.py");
129  12 if (is != null) {
130  12 try {
131  12 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  12 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  12 this.engine = scriptEngineManager.getEngineByName(ENGINE_ID);
143   
144  12 if (this.engine == null) {
145  0 throw new InitializationException("Failed to find engine for Python script language");
146    }
147   
148  12 String highlightSyntaxId = getSyntaxId() + "-highlight";
149  12 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  12 toggle @Override
159    public List<Block> highlight(String syntaxId, Reader source) throws ParseException
160    {
161  12 String code;
162  12 try {
163  12 code = IOUtils.toString(source);
164    } catch (IOException e) {
165  0 throw new ParseException("Failed to read source", e);
166    }
167   
168  12 if (code.length() == 0) {
169  1 return Collections.emptyList();
170    }
171   
172  11 List<Block> blocks;
173  11 try {
174  11 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  11 if (code.charAt(code.length() - 1) != '\n' && !blocks.isEmpty()
181    && blocks.get(blocks.size() - 1) instanceof NewLineBlock) {
182  10 blocks.remove(blocks.size() - 1);
183    }
184   
185  11 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  11 toggle private List<Block> highlight(String syntaxId, String code) throws ScriptException, ParseException
198    {
199  11 BlocksGeneratorPygmentsListener listener = new BlocksGeneratorPygmentsListener(this.plainTextParser);
200   
201  11 ScriptContext scriptContext = new SimpleScriptContext();
202   
203  11 scriptContext.setAttribute(PY_LANGUAGE_VARNAME, syntaxId, ScriptContext.ENGINE_SCOPE);
204  11 scriptContext.setAttribute(PY_CODE_VARNAME, code, ScriptContext.ENGINE_SCOPE);
205  11 scriptContext.setAttribute(PY_STYLE_VARNAME, this.configuration.getStyle(), ScriptContext.ENGINE_SCOPE);
206  11 scriptContext.setAttribute(PY_LISTENER_VARNAME, listener, ScriptContext.ENGINE_SCOPE);
207   
208  11 this.engine.eval(this.script, scriptContext);
209   
210  11 List<Block> blocks;
211  11 if (scriptContext.getAttribute(PY_LEXER_VARNAME) != null) {
212  10 blocks = listener.getBlocks();
213    } else {
214  1 blocks = this.plainTextParser.parse(new StringReader(code)).getChildren().get(0).getChildren();
215    }
216   
217  11 return blocks;
218    }
219    }