开放的编程资料库

当前位置:我爱分享网 > PHP教程 > 正文

PHP 和 WordPress 上的 Pygments

我一直在寻找一个很棒的代码高亮器,我一直在使用很多我什至不记得的东西。这些是我现在能记住的:

  • SyntaxHighlighter
  • Google Prettifier
  • highlighter.js
  • Geshi

现在我正在使用 highlighter.js 但这并不是我想要的,我想要的是能够突出显示大多数“单词”或保留字,例如 built in功能,对象等,这个荧光笔和大部分都丢失了。我知道这不是一件重要的事情,不幸的是,这一直困扰着我,直到现在。

最后,我发现 Pygments 是与我一直在寻找的东西相匹配的完美选择,它与 GitHub 使用的相同。现在唯一的障碍是它是一个基于 python 的语法荧光笔,而我使用的是 WordPress,而 WordPress 是基于 PHP 构建的。

安装

但是,嘿,我们可以克服它,有一个解决方案,首先,我们需要在我们的服务器上安装 python 以便我们可以使用 Pygments。

我们不会深入介绍安装,因为存在如此多的操作系统风格,并且每个操作系统风格可能略有不同。

蟒蛇

首先,您必须通过在命令行中输入 python 来检查您是否已经安装了 python。

如果未安装,您应该查看 Python 下载页面并下载您的操作系统安装程序。

画中画安装程序

根据其站点安装pip installer,有两种安装方式:

第一个也是推荐的方法是下载 get-pip.py 并在命令行上运行它:

python get-pip.py

第二种方法是使用包管理器,通过运行这两个可能的命令之一,就像之前提到的那样,这取决于您的服务器操作系统。

sudo apt-get install python-pip

或者:

sudo yum install python-pip

注意:您可以使用您喜欢的任何包管理器,例如 easy_install,为了举例,因为是 Pygments 站点上使用的那个,我使用了 pip。

颜料

要安装 pygments,你需要运行这个命令:

pip install Pygments

如果您所在的服务器上的用户没有 root 访问权限,您将无法使用前面的命令安装它,如果是这种情况,您必须使用 --user 在用户目录上安装模块的标志。

pip install --user Pygments

现在一切都已安装,所以我们要做的是使用 PHP 和一些 Python 代码

PHP + Python

它的工作方式是通过 php 使用 exec() 发送语言名称和包含要突出显示的代码的文件的文件名来执行 python 脚本。

蟒蛇

我们要做的第一件事是创建 python 脚本,该脚本将使用 Pygments 将纯代码转换为突出显示的代码。

那么让我们逐步了解如何创建 python 脚本。

首先我们导入所有需要的模块:

import sys
from pygments import highlight
from pygments.formatters import HtmlFormatter

sys 模块提供 argv 列表,其中包含传递给 python 脚本的所有参数。

pygments 中的

highlight 实际上是 main 函数以及 lexer 将生成突出显示的代码。您会在下面阅读更多关于词法分析器的内容。

HtmlFormatter 是我们希望生成的代码格式化的方式,我们将使用 HTML 格式。以下是可用格式化程序的列表,以备不时之需。

# Get the code
language = (sys.argv[1]).lower()
filename = sys.argv[2] 
f = open(filename, 'rb')
code = f.read()
f.close()

此代码块的作用是获取第二个参数 (sys.argv[1]) 并将其转换为小写文本,以确保它始终为小写。因为 "php" !== "PHP"。第三个参数 sys.argv[2] 是代码的文件名路径,所以我们打开,读取它的内容并关闭它。第一个参数是 python 的脚本名称。

# Importing Lexers
# PHP
if language == 'php':
  from pygments.lexers import PhpLexer
  lexer = PhpLexer(startinline=True)

# GUESS
elif language == 'guess':
  from pygments.lexers import guess_lexer
  lexer = guess_lexer( code )

# GET BY NAME
else:
  from pygments.lexers import get_lexer_by_name
  lexer = get_lexer_by_name( language )

所以是时候导入词法分析器了,这段代码的作用是根据我们需要分析的语言创建词法分析器。词法分析器它的作用是分析我们的代码并获取每个保留字、符号、内置函数等。

在这种情况下,在词法分析器分析之后,所有代码都将格式化为 HTML,将所有“单词”包装到带有类的 HTML 元素中。顺便说一句,类名根本没有描述性,所以函数不是类“函数”,但无论如何,现在这不是什么值得担心的事情。

变量 language 包含我们要转换代码的语言名称的字符串,我们使用 lexer = get_lexer_by_name( language ) 通过名称获取任何词法分析器,好吧,它的功能不言自明。但是为什么我们首先检查 phpguess 你可能会问,好吧,我们检查 php 因为如果我们使用 get_lexer_by_name('php')并且 php 代码没有所需的开始 php 标记 <?php 不会很好地突出显示代码或如我们预期的那样,我们需要创建一个特定的 php像这样的词法分析器 lexer = PhpLexer(startinline=True) 传递 startinline=True 作为参数,所以不再需要这个开始的 php 标签。 guess 是我们从 php 传递给 pygments 的字符串,我们不知道它是哪种语言,或者没有提供语言,我们需要猜测它。

在他们的网站上有一个可用的词法分析器列表。

python 的最后一步是创建 HTML 格式化程序,执行突出显示并输出包含突出显示代码的 HTML 代码。

formatter = HtmlFormatter(linenos=False, encoding='utf-8', nowrap=True)
highlighted = highlight(code, lexer, formatter)
print highlighted

对于格式化程序,传递 linenos=False 以不生成行号,传递 nowrap=True 以不允许 div 包装生成代码。这是个人决定,代码将使用 PHP 进行包装。

接下来它传递包含实际代码的 code,包含语言词法分析器的 lexer 和我们刚刚在上面的行中创建的 formatter突出显示我们希望如何格式化代码。

最后输出代码。

这就是关于 python 的,即要构建亮点的脚本。

这是完整的文件:build.py

import sys
from pygments import highlight
from pygments.formatters import HtmlFormatter


# If there isn't only 2 args something weird is going on
expecting = 2;
if ( len(sys.argv) != expecting + 1 ):
  exit(128)

# Get the code
language = (sys.argv[1]).lower()
filename = sys.argv[2] 
f = open(filename, 'rb')
code = f.read()
f.close()


# PHP
if language == 'php':
  from pygments.lexers import PhpLexer
  lexer = PhpLexer(startinline=True)

# GUESS
elif language == 'guess':
  from pygments.lexers import guess_lexer
  lexer = guess_lexer( code )

# GET BY NAME
else:
  from pygments.lexers import get_lexer_by_name
  lexer = get_lexer_by_name( language )
  

# OUTPUT
formatter = HtmlFormatter(linenos=False, encoding='utf-8', nowrap=True)
highlighted = highlight(code, lexer, formatter)
print highlighted

PHP-WordPress

让我们跳转到 WordPress 并创建一个基本插件来处理需要突出显示的代码。

如果你一辈子都没有为WordPress创建过插件也没关系,这个插件只是一个文件,里面有php函数,所以你没有WordPress插件开发知识也没关系,但你需要知识尽管在 WordPress 开发上。

wp-content/plugins 中创建一个名为 wp-pygments 的文件夹(可以是任何你想要的)并在其中复制 build.py 我们刚刚创建的 python 脚本并创建一个名为 wp-pygments.php 的新 php 文件(可能与目录同名)。

下面的代码只是让 WordPress 知道插件的名称和其他信息,这段代码将位于 wp-pygments.php 的顶部。

<?php
/*
 * Plugin Name: WP Pygments
 * Plugin URI: http://wellingguzman.com/wp-pygments
 * Description: A brief description of the Plugin.
 * Version: 0.1
 * Author: Welling Guzman
 * Author URI: http://wellingguzman.com
 * License: MEH
*/
?>

the_content 上添加过滤器以查找

 标签。预期的代码是:

<pre >
<code>
$name = "World";
echo "Hello, " . $name;
</code>
</pre>

注意:html标签需要编码;例如 < 需要是 < 这样解析就不会混淆,也不会做错了。

其中classpre标签中代码的语言,如果没有class或者是空的会把guess传递给build.py.

add_filter( 'the_content', 'mb_pygments_content_filter' );
function mb_pygments_content_filter( $content )
{
  $content = preg_replace_callback('/
]?.*?>.*?<code>(.*?)<\/code>.*?<\/pre>/sim', 'mb_pygments_convert_code', $content);
  
  return $content;
}

preg_replace_callback 函数将在每次使用提供的正则表达式模式匹配内容时执行 mb_pygments_convert_code 回调函数:/

]?.*?>.*?(.*?).*?/sim,它应该匹配帖子/页面内容上的任何 

sim 呢?这是三个模式修饰符标志。来自 php.net:

  • s:如果设置了这个修饰符,则模式中的点元字符匹配所有字符,包括换行符。
  • i:如果设置了此修饰符,则模式中的字母同时匹配大写和小写字母。
  • m:默认情况下,PCRE 将主题字符串视为由单个“行”组成字符(即使它实际上包含几个换行符)。

这也可以用 DOMDocument(); 来完成。替换 /

]?.*?>.*?(.*?).*?/sim与此:

// This prevent throwing error
libxml_use_internal_errors(true);

// Get all pre from post content
$dom = new DOMDocument();
$dom->loadHTML($content);
$pres = $dom->getElementsByTagName('pre');

foreach ($pres as $pre) {
  $class = $pre->attributes->getNamedItem('class')->nodeValue;
  $code = $pre->nodeValue;
  
  $args = array(
    2 => $class, // Element at position [2] is the class
    3 => $code // And element at position [2] is the code
  );
  
  // convert the code
  $new_code = mb_pygments_convert_code($args);
  
  // Replace the actual pre with the new one.
  $new_pre = $dom->createDocumentFragment();
  $new_pre->appendXML($new_code);
  $pre->parentNode->replaceChild($new_pre, $pre);
}
// Save the HTML of the new code.
$content = $dom->saveHTML();

下面的代码来自 mb_pygments_convert_code 函数。

define( 'MB_WPP_BASE', dirname(__FILE__) );
function mb_pygments_convert_code( $matches )
{
  $pygments_build = MB_WPP_BASE . '/build.py';
  $source_code    = isset($matches[3])?$matches[3]:'';
  $class_name     = isset($matches[2])?$matches[2]:'';
  
  // Creates a temporary filename
  $temp_file      = tempnam(sys_get_temp_dir(), 'MB_Pygments_');
  
  // Populate temporary file
  $filehandle = fopen($temp_file, "w");
  fwrite($filehandle, html_entity_decode($source_code, ENT_COMPAT, 'UTF-8') );
  fclose($filehandle);
  
  // Creates pygments command
  $language   = $class_name?$class_name:'guess';
  $command    = sprintf('python %s %s %s', $pygments_build, $language, $temp_file);

  // Executes the command
  $retVal = -1;
  exec( $command, $output, $retVal );
  unlink($temp_file);
  
  // Returns Source Code
  $format = '<div ><pre><code>%s</code></pre></div>';
  
  if ( $retVal == 0 )
    $source_code = implode("\n", $output);
    
  $highlighted_code = sprintf($format, $language, $source_code);
  
  return $highlighted_code;
}

查看上面的代码:

define( 'MB_WPP_BASE', dirname(__FILE__) );

定义一个绝对插件的目录路径常量。

$pygments_build = MB_WPP_BASE . '/build.py';
$source_code    = isset($matches[3])?$matches[3]:'';
$class_name     = isset($matches[2])?$matches[2]:'';

$pygments_build 是python脚本所在的完整路径。每次匹配时,都会传递一个名为 $matches 的数组,其中包含 4 个元素。以此作为来自帖子/页面内容的匹配代码的示例:

<pre >
<code>
$name = "World";
echo "Hello, " . $name;
</code>
</pre>
  • 位置[0]的元素为整个

    <pre >
    <code>
    $name = "World";
    echo "Hello, " . $name;
    </code>
    </pre>
    
  • 位置[1]的元素是类属性名及其值,其值为:

    
    
  • 位置[2]的元素是没有名字的类属性值,它的值为:

    php
    
  • p>位置[3]的元素是去掉pre标签的代码本身,其值为:

    $name = "World";
    echo "Hello, " . $name;
    
// Creates a temporary filename
$temp_file = tempnam(sys_get_temp_dir(), 'MB_Pygments_');

它创建一个临时文件,其中包含将传递给 python 脚本的代码。这是处理代码的更好方法。如果不将整个事情作为参数传递,那将是一团糟。

// Populate temporary file
$filehandle = fopen($temp_file, "wb");
fwrite($filehandle, html_entity_decode($source_code, ENT_COMPAT, 'UTF-8') );
fclose($filehandle);

它创建代码文件,但我们解码所有 HTML 实体,因此 pygments 可以正确转换它们。

// Creates pygments command
$language = $class_name?$class_name:'guess';
$command  = sprintf('python %s %s %s', $pygments_build, $language, $temp_file);

它创建要使用的 python 命令,它输出:

python /path/to/build.py php /path/to/temp.file
// Executes the command
$retVal = -1;
exec( $command, $output, $retVal );
unlink($temp_file);
  
// Returns Source Code
$format = '<div ><pre><code>%s</code></pre></div>';
  
if ( $retVal == 0 )
  $source_code = implode("\n", $output);
    
$highlighted_code = sprintf($format, $language, $source_code);

执行刚刚创建的命令,如果返回 0,则在 python 脚本上一切正常。 exec(); 返回 python 脚本输出的行数组。所以我们将数组输出连接成一个字符串作为源代码。如果没有,我们将坚持使用没有突出显示的代码。

通过缓存改进

现在工作正常,但我们必须节省时间和处理,想象一下内容上的 100 个

 标签会创建 100 个文件并调用 100 次 python 脚本,所以让我们缓存这个宝贝。

瞬态 API

WordPress 使用 Transient API 提供在数据库中临时存储数据的能力。

首先,让我们向 save_post 挂钩添加一个操作,这样每次保存帖子时我们都会转换代码并缓存它。

add_action( 'save_post', 'mb_pygments_save_post' );
function mb_pygments_save_post( $post_id )
{
  if ( wp_is_post_revision( $post_id ) )
    return;
    
  $content = get_post_field( 'post_content', $post_id );
  
  mb_pygments_content_filter( $content );
}

如果是修订我们什么都不做,否则我们获取帖子内容并调用 pygments 内容过滤器函数。

让我们创建一些函数来处理缓存。

// Cache Functions
// Expiration time (1 month), let's clear cache every month.
define('MB_WPP_EXPIRATION', 60 * 60 * 24 * 30);

// This function it returns the name of a post cache.
function get_post_cache_transient()
{
  global $post;
  
  $post_id = $post->ID;
  $transient = 'post_' . $post_id . '_content';
  
  return $transient;
}

// This creates a post cache for a month,
// containing the new content with pygments
// and last time the post was updated.
function save_post_cache($content)
{
  global $post;
    
  $expiration = MB_WPP_EXPIRATION;
  $value = array( 'content'=>$content, 'updated'=>$post->post_modified );
  set_transient( get_post_cache_transient(), $value, $expiration );
}

// This returns a post cache
function get_post_cache()
{
  $cached_post = get_transient( get_post_cache_transient() );
  
  return $cached_post;
}

// Check if a post needs to be updated.
function post_cache_needs_update()
{
  global $post;
  
  $cached_post = get_post_cache();
  if ( strtotime($post->post_modified) > strtotime($cached_post['updated']) )
    return TRUE;
      
  return FALSE;
}

// Delete a post cache.
function clear_post_cache()
{ 
  delete_transient( get_post_cache_transient() );
}

mb_pygments_content_filter() 的开头添加一些行来检查是否有缓存的帖子。

function mb_pygments_content_filter( $content )
{
  if ( FALSE !== ( $cached_post = get_post_cache() ) && !post_cache_needs_update() )
    return $cached_post['content'];

  clear_post_cache();

并在 mb_pygments_content_filter() 的末尾添加一行以保存帖子缓存。

save_post_cache( $content );

最后,当插件被卸载时,我们需要删除所有我们创建的缓存,这有点棘手,所以我们使用 $wpdb 对象来删除所有使用这个查询。

register_uninstall_hook(__FILE__, 'mb_wp_pygments_uninstall');
function mb_wp_pygments_uninstall() {
  global $wpdb;
  
  $wpdb->query( "DELETE FROM `wp_options` WHERE option_name LIKE '_transient_post_%_content' " );
}
未经允许不得转载:我爱分享网 » PHP 和 WordPress 上的 Pygments

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏