Hatena::Groupperl

Perl Tech RSSフィード

2013-01-13

PegexでCSSを解析するcss.pgxを作成しました はてなブックマーク -  PegexでCSSを解析するcss.pgxを作成しました - Perl Tech

用事があって複数のCSS文字列をマージする機能が必要になり、Ingy döt Net氏作のPegexというモジュール用のCSS文法を作ってみました。(CSSをマージするプログラムは探せばあるけれど、自分好みに解析したいし、perlで完結させたかったので。)Pegexを使う参考になるかと思ったので公開します。コピー・改変はご自由に。

注意

この文法はまだ開発中のため、CSSの変な箇所にコメントがあるとエラーになります。たとえば、セレクターの途中でh1, /* */ h2 { }のようにするとエラーで止まります。blank: comment | ~ のような文法を追加して使えば大丈夫かもしれませんが、面倒なのでまだ手を付けていません

css.pgx自体は単なる文法定義なので、これだけでは何の役にも立ちません。レシーバーモジュール(Pegex::Tree::Wrapなど)をカスタマイズすることで初めて真価を発揮します。専用のレシーバーを作れば、以下のような応用が可能になります。

  • マージ
  • 整形
  • プリプロセッサー
  • フォーマット変換

インストール方法

# Grammar for Cascading Style Sheets Level 1-3
# Copyright (c) 2013 Toshiyuki Yamato. <toshiyuki.yamato@gmail.com>

%grammar css
%version 0.1.0

css: -<outline>* eof

outline: -<at_rule> | rule | comment | ~~

eof: / ~ <EOS> /

ident: / ( <ALPHA> [<UNDER><ALNUMS><DASH>]* ) /

namespace: / (: <ident> <PIPE> )? /

element: namespace / <ident> /

class: / <DOT><ident> /

id: / <HASH><ident> /

all: / ( <STAR> ) /

property: / ( [<DASH><ALPHAS>]+ ) /

value: url | format | -<color> | ident | quoted_string | length | percentage | number

value_combination: -<value>+ % ~~

value_group: value_combination+ % / ~ <COMMA> ~ /

declaration: property / ~ <COLON> ~ / value_group ~ important / ~ <SEMI> /

important: / ( <BANG> ~ important )? /

block: / <LCURLY> ~ / ( declaration | comment | ~~ )* / ~ <RCURLY> /

selector: ( all | element | class | id | attribute | pseudo )+

connector: / ( [<WS><RANGLE><PLUS><TILDE>]+ ) (! [<COMMA><LCURLY>] ) /

selector_combination: -<selector>+ % connector

selector_group: selector_combination+ % / ~ <COMMA> ~ /

rule: selector_group ~ block

argument: / (: <LPAREN> ( [^<RPAREN>]* ) <RPAREN> )? /

pseudo: / <COLON><COLON>? <ident> / argument

quoted_string: / [<SINGLE><DOUBLE>] ( [^<SINGLE><DOUBLE>]* ) [<SINGLE><DOUBLE>] /

attribute: / <LSQUARE> ~ / ( match | element ) / ~ <RSQUARE> /

match: / <ident> ~ ( [<TILDE><PIPE><CARET><DOLLAR><STAR>]? <EQUAL> ) ~ <quoted_string> /

comment: /
    <SLASH><STAR> (
    [^<STAR>]* (: (! <STAR><SLASH> ) <STAR> [^<STAR>]* )*
    ) <STAR><SLASH> ~
/

url: / url <argument> /

format: / format <LPAREN> ~ <quoted_string> ~ <RPAREN> /

decimal: / <DASH>? <DIGIT>+ (: <DOT><DIGIT>+ )? /

number: / ( <decimal> ) /

length: / ( <decimal> (: em | ex | ch | rem | vw | vh | vmin | cm | mm | in | px | pt | pc) )  /

color: rrggbb_hex | rgb_hex | rgba | rgb | hsla | hsl

percentage: / ( <decimal> <PERCENT> ) /

rgb_value: percentage | number

rrggbb_hex: / ( <HASH> <HEX>{6} ) /

rgb_hex: / ( <HASH> <HEX>{3} ) /

rgb:  / rgb  <LPAREN> ~ / ( -<rgb_value>3 % / ~ <COMMA> ~ / ) / ~ <RPAREN> /

rgba: / rgba <LPAREN> ~ / ( -<rgb_value>4 % / ~ <COMMA> ~ / ) / ~ <RPAREN> /

hsl:  / hsl  <LPAREN> ~ / ( -<rgb_value>3 % / ~ <COMMA> ~ / ) / ~ <RPAREN> /

hsla: / hsla <LPAREN> ~ / ( -<rgb_value>4 % / ~ <COMMA> ~ / ) / ~ <RPAREN> /

at_rule:
    at_charset | at_font_face | at_import | at_media | at_namespace |
    at_page | at_unknown

at_charset: / <AT>charset ~ <quoted_string> ~ <SEMI> /

at_font_face: / <AT>font-face ~ / block

at_import: / <AT>import ~ / ( url | quoted_string ) / ~ <SEMI> /

at_media:
/ <AT>media ~ / ( -<ident>+ % / ~ <COMMA> ~ / )
/ ~ <LCURLY> ~ /
-<outline>*
/ ~ <RCURLY> /

at_namespace: / <AT>namespace ~ <ident>? ~ <quoted_string> ~ <SEMI> /

at_page: / <AT>page ~ ( <COLON> (: first | right | left ) )? / block

at_unknown: / <AT><ident> ~ <LCURLY> ~ / -<outline>* / ~ <RCURLY> /

使用例

文法ファイルは以下のように使用する。

use strict;
use File::Slurp;
use Data::Dumper qw(Dumper);
$Data::Dumper::Indent = 1;

use Pegex;

my $grammer = read_file "css.pgx";

# CSSをファイルから読み込む
# my $input = Pegex::Input->new(file => "mycss.css");

# 文字列を直接解析したい場合はこっち
my $input = Pegex::Input->new(string => <<'CSS');
@charset "UTF-8";

h1, p {
    font-style: normal;
}

CSS

my $parser = pegex($grammer, receiver => "Pegex::Tree::Wrap");
my $data = $parser->parse($input);
write_file "output.txt", Dumper $data;

すると、出力は以下のようになります。CSS3に対応させるため、要素セレクタにnamespaceなどが付与されるようになっています(この例では名前空間が未指定なのでundefになる)。

$VAR1 = {
  'css' => [
    [
      {
        'at_charset' => 'UTF-8'
      },
      {
        'rule' => [
          {
            'selector_group' => [
              {
                'selector_combination' => [
                  [
                    {
                      'element' => [
                        {
                          'namespace' => undef
                        },
                        'h1'
                      ]
                    }
                  ]
                ]
              },
              {
                'selector_combination' => [
                  [
                    {
                      'element' => [
                        {
                          'namespace' => undef
                        },
                        'p'
                      ]
                    }
                  ]
                ]
              }
            ]
          },
          {
            'block' => [
              [
                {
                  'declaration' => [
                    {
                      'property' => 'font-style'
                    },
                    {
                      'value_group' => [
                        {
                          'value_combination' => [
                            {
                              'ident' => 'normal'
                            }
                          ]
                        }
                      ]
                    },
                    {
                      'important' => undef
                    }
                  ]
                }
              ]
            ]
          }
        ]
      }
    ]
  ]
};