[vlc-commits] codec: webvtt: add CSS lexer/grammar/parser

Francois Cartegnie git at videolan.org
Tue Nov 7 12:37:32 CET 2017


vlc | branch: master | Francois Cartegnie <fcvlcdev at free.fr> | Mon Oct  9 11:27:15 2017 +0200| [649b3d02f50855953cd19b5bdbfee177ffba38d3] | committer: Francois Cartegnie

codec: webvtt: add CSS lexer/grammar/parser

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=649b3d02f50855953cd19b5bdbfee177ffba38d3
---

 configure.ac                      |  19 +
 modules/codec/Makefile.am         |  12 +
 modules/codec/webvtt/CSSGrammar.y | 773 ++++++++++++++++++++++++++++++++++++++
 modules/codec/webvtt/CSSLexer.l   | 107 ++++++
 modules/codec/webvtt/css_bridge.h |  27 ++
 modules/codec/webvtt/css_parser.c | 517 +++++++++++++++++++++++++
 modules/codec/webvtt/css_parser.h | 182 +++++++++
 7 files changed, 1637 insertions(+)

diff --git a/configure.ac b/configure.ac
index 579ed76dab..6f40d056f5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2956,6 +2956,25 @@ AS_IF([test "${enable_tiger}" != "no"], [
   ])
 ])
 
+dnl
+dnl  check if we can build the css selector engine 
+dnl
+AC_ARG_ENABLE(css,
+[  --enable-css            CSS selector engine (default auto)])
+AS_IF([test "${enable_css}" != "no"], [
+  AC_PROG_LEX
+  with_css="no"
+  AS_IF([test "x$LEX" != "x:"], [
+    AC_PROG_YACC
+    dnl can only accept bison or we need byacc feature test for %destructor
+    AS_IF([test "x$YACC" = "xbison -y"], [
+      AC_DEFINE(HAVE_CSS, 1, [Define if CSS engine is built])]
+      with_css="yes"
+    ,[AC_MSG_WARN("Bison not found. CSS support disabled.")])
+  ])
+])
+AM_CONDITIONAL([ENABLE_CSS], [test "${with_css}" = "yes"])
+
 
 dnl
 dnl  Video plugins
diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am
index 2f625badfb..ca62716bdd 100644
--- a/modules/codec/Makefile.am
+++ b/modules/codec/Makefile.am
@@ -228,6 +228,18 @@ libwebvtt_plugin_la_SOURCES = codec/webvtt/subsvtt.c \
                               demux/webvtt.c \
                               demux/mp4/minibox.h
 codec_LTLIBRARIES += libwebvtt_plugin.la
+AM_YFLAGS = -d
+if ENABLE_CSS
+SUFFIXES += .l .y
+# dependency: we need grammar build first
+BUILT_SOURCES += codec/webvtt/CSSGrammar.h
+libwebvtt_plugin_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/codec/webvtt/ -I$(builddir)/codec/webvtt/
+libwebvtt_plugin_la_SOURCES += codec/webvtt/CSSGrammar.y \
+                               codec/webvtt/CSSLexer.l \
+                               codec/webvtt/css_parser.c \
+                               codec/webvtt/css_parser.h \
+                               codec/webvtt/css_bridge.h
+endif
 
 libsvcdsub_plugin_la_SOURCES = codec/svcdsub.c
 codec_LTLIBRARIES += libsvcdsub_plugin.la
diff --git a/modules/codec/webvtt/CSSGrammar.y b/modules/codec/webvtt/CSSGrammar.y
new file mode 100644
index 0000000000..00e0babc3f
--- /dev/null
+++ b/modules/codec/webvtt/CSSGrammar.y
@@ -0,0 +1,773 @@
+/*****************************************************************************
+ * CSSGrammar.y: bison production rules for simplified css parsing
+ *****************************************************************************
+ *  Copyright ©   2017 VideoLabs, VideoLAN and VLC Authors
+ *
+ *  Adapted from webkit's CSSGrammar.y:
+ *
+ *  Copyright (C) 2002-2003 Lars Knoll (knoll at kde.org)
+ *  Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
+ *  Copyright (C) 2006 Alexey Proskuryakov (ap at nypop.com)
+ *  Copyright (C) 2008 Eric Seidel <eric at webkit.org>
+ *  Copyright (C) 2012 Intel Corporation. All rights reserved.
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+%pure-parser
+
+%parse-param { yyscan_t scanner } { vlc_css_parser_t *css_parser }
+%lex-param   { yyscan_t scanner } { vlc_css_parser_t *css_parser }
+
+%{
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <vlc_common.h>
+#include "css_parser.h"
+
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void* yyscan_t;
+#endif
+%}
+
+%union {
+    bool boolean;
+    char character;
+    int integer;
+    char *string;
+    enum vlc_css_relation_e relation;
+
+    vlc_css_term_t term;
+    vlc_css_expr_t *expr;
+    vlc_css_rule_t  *rule;
+    vlc_css_declaration_t *declaration;
+    vlc_css_declaration_t *declarationList;
+    vlc_css_selector_t *selector;
+    vlc_css_selector_t *selectorList;
+}
+
+%{
+/* See bison pure calling */
+int yylex(union YYSTYPE *, yyscan_t, vlc_css_parser_t *);
+
+static int yyerror(yyscan_t scanner, vlc_css_parser_t *p, const char *msg)
+{
+    VLC_UNUSED(scanner);VLC_UNUSED(p);VLC_UNUSED(msg);
+    return 1;
+}
+
+%}
+
+%expect 10
+
+%nonassoc LOWEST_PREC
+
+%left UNIMPORTANT_TOK
+
+%token WHITESPACE SGML_CD
+%token TOKEN_EOF 0
+
+%token INCLUDES
+%token DASHMATCH
+%token BEGINSWITH
+%token ENDSWITH
+%token CONTAINS
+
+%token <string> STRING
+%right <string> IDENT
+
+%nonassoc <string> IDSEL
+%nonassoc <string> HASH
+%nonassoc ':'
+%nonassoc '.'
+%nonassoc '['
+%nonassoc <character> '*'
+%nonassoc error
+%left '|'
+
+%token FONT_FACE_SYM
+%token CHARSET_SYM
+
+%token IMPORTANT_SYM
+
+%token CDO
+%token CDC
+%token <term> LENGTH
+%token <term> ANGLE
+%token <term> TIME
+%token <term> FREQ
+%token <term> DIMEN
+%token <term> PERCENTAGE
+%token <term> NUMBER
+
+%destructor { vlc_css_term_Clean($$); } <term>
+
+%token <string> URI
+%token <string> FUNCTION
+%token <string> UNICODERANGE
+
+%type <relation> combinator
+
+%type <rule> charset
+%type <rule> ignored_charset
+%type <rule> ruleset
+%type <rule> font_face
+%type <rule> invalid_rule
+%type <rule> rule
+%type <rule> valid_rule
+%destructor { vlc_css_rules_Delete($$); } <rule>
+
+%type <string> ident_or_string
+%type <string> property
+
+%type <selector> specifier
+%type <selector> specifier_list
+%type <selector> simple_selector
+%type <selector> selector
+%type <selectorList> selector_list
+%type <selector> selector_with_trailing_whitespace
+%type <selector> class
+%type <selector> attrib
+%type <selector> pseudo
+%destructor { vlc_css_selectors_Delete($$); } <selector> <selectorList>
+
+%type <declarationList> declaration_list
+%type <declarationList> decl_list
+%type <declaration> declaration
+%destructor { vlc_css_declarations_Delete($$); } <declaration> <declarationList>
+
+%type <boolean> prio
+
+%type <integer> match
+%type <integer> unary_operator
+%type <integer> maybe_unary_operator
+%type <character> operator
+
+%type <expr> expr
+%type <term> term
+%type <term> unary_term
+%type <term> function
+%destructor { vlc_css_expression_Delete($$); } <expr>
+
+%type <string> element_name
+%type <string> attr_name
+
+%destructor { free($$); } <string>
+
+%%
+
+stylesheet:
+    maybe_space maybe_charset maybe_sgml rule_list
+  ;
+
+maybe_space:
+    /* empty */ %prec UNIMPORTANT_TOK
+  | maybe_space WHITESPACE
+  ;
+
+maybe_sgml:
+    /* empty */
+  | maybe_sgml SGML_CD
+  | maybe_sgml WHITESPACE
+  ;
+
+maybe_charset:
+   /* empty */
+  | charset {
+    vlc_css_rules_Delete($1);
+  }
+  ;
+
+closing_brace:
+    '}'
+  | %prec LOWEST_PREC TOKEN_EOF
+  ;
+
+charset:
+  CHARSET_SYM maybe_space STRING maybe_space ';' {
+      free( $3 );
+      $$ = 0;
+  }
+  | CHARSET_SYM error invalid_block {
+      $$ = 0;
+  }
+  | CHARSET_SYM error ';' {
+      $$ = 0;
+  }
+;
+
+ignored_charset:
+    CHARSET_SYM maybe_space STRING maybe_space ';' {
+        // Ignore any @charset rule not at the beginning of the style sheet.
+        free( $3 );
+        $$ = 0;
+    }
+    | CHARSET_SYM maybe_space ';' {
+        $$ = 0;
+    }
+;
+
+rule_list:
+   /* empty */
+ | rule_list rule maybe_sgml {
+     if( $2 )
+         vlc_css_parser_AddRule( css_parser, $2 );
+ }
+ ;
+
+valid_rule:
+    ruleset
+  | font_face
+  ;
+
+rule:
+    valid_rule {
+        $$ = $1;
+        if($$)
+            $$->b_valid = true;
+    }
+  | ignored_charset
+  | invalid_rule
+  ;
+
+font_face:
+    FONT_FACE_SYM maybe_space
+    '{' maybe_space declaration_list closing_brace {
+        vlc_css_declarations_Delete( $5 );
+        $$ = NULL;
+    }
+    | FONT_FACE_SYM error invalid_block {
+        $$ = NULL;
+    }
+    | FONT_FACE_SYM error ';' {
+        $$ = NULL;
+    }
+;
+
+combinator:
+    '+' maybe_space { $$ = RELATION_DIRECTADJACENT; }
+  | '~' maybe_space { $$ = RELATION_INDIRECTADJACENT; }
+  | '>' maybe_space { $$ = RELATION_CHILD; }
+  ;
+
+maybe_unary_operator:
+    unary_operator { $$ = $1; }
+    | { $$ = 1; }
+    ;
+
+unary_operator:
+    '-' { $$ = -1; }
+  | '+' { $$ = 1; }
+  ;
+
+ruleset:
+    selector_list '{' maybe_space declaration_list closing_brace {
+        $$ = vlc_css_rule_New();
+        if($$)
+        {
+            $$->p_selectors = $1;
+            $$->p_declarations = $4;
+        }
+    }
+  ;
+
+selector_list:
+    selector %prec UNIMPORTANT_TOK {
+        if ($1) {
+            $$ = $1;
+        }
+    }
+    | selector_list ',' maybe_space selector %prec UNIMPORTANT_TOK {
+        if ($1 && $4 )
+        {
+            $$ = $1;
+            vlc_css_selector_Append( $$, $4 );
+        }
+        else
+        {
+            vlc_css_selectors_Delete( $1 );
+            vlc_css_selectors_Delete( $4 );
+            $$ = NULL;
+        }
+    }
+  | selector_list error {
+        vlc_css_selectors_Delete( $1 );
+        $$ = NULL;
+    }
+   ;
+
+selector_with_trailing_whitespace:
+    selector WHITESPACE {
+        $$ = $1;
+    }
+    ;
+
+selector:
+    simple_selector {
+        $$ = $1;
+    }
+    | selector_with_trailing_whitespace
+    {
+        $$ = $1;
+    }
+    | selector_with_trailing_whitespace simple_selector
+    {
+        $$ = $1;
+        if ($$)
+        {
+            vlc_css_selector_AddSpecifier( $$, $2 );
+            $2->combinator = RELATION_DESCENDENT;
+        }
+        else $$ = $2;
+    }
+    | selector combinator simple_selector {
+        $$ = $1;
+        if ($$)
+        {
+            vlc_css_selector_AddSpecifier( $$, $3 );
+            $3->combinator = $2;
+        }
+        else $$ = $3;
+    }
+    | selector error {
+        vlc_css_selectors_Delete( $1 );
+        $$ = NULL;
+    }
+    ;
+
+simple_selector:
+    element_name {
+        $$ = vlc_css_selector_New( SELECTOR_SIMPLE, $1 );
+        free( $1 );
+    }
+    | element_name specifier_list {
+        $$ = vlc_css_selector_New( SELECTOR_SIMPLE, $1 );
+        if( $$ && $2 )
+        {
+            vlc_css_selector_AddSpecifier( $$, $2 );
+        }
+        else
+        {
+            vlc_css_selectors_Delete( $2 );
+        }
+        free( $1 );
+    }
+    | specifier_list {
+        $$ = $1;
+    }
+  ;
+
+element_name:
+    IDENT
+    | '*' { $$ = strdup("*"); }
+  ;
+
+specifier_list:
+    specifier {
+        $$ = $1;
+    }
+    | specifier_list specifier {
+        if( $1 )
+        {
+            $$ = $1;
+            while( $1->specifiers.p_first )
+                $1 = $1->specifiers.p_first;
+            vlc_css_selector_AddSpecifier( $1, $2 );
+        }
+        else $$ = $2;
+    }
+    | specifier_list error {
+        vlc_css_selectors_Delete( $1 );
+        $$ = NULL;
+    }
+;
+
+specifier:
+    IDSEL {
+        $$ = vlc_css_selector_New( SPECIFIER_ID, $1 );
+        free( $1 );
+    }
+    /* Case when #fffaaa like token is lexed as HEX instead of IDSEL */
+  | HASH {
+        if ($1[0] >= '0' && $1[0] <= '9') {
+            $$ = NULL;
+        } else {
+            $$ = vlc_css_selector_New( SPECIFIER_ID, $1 );
+        }
+        free( $1 );
+    }
+  | class
+  | attrib
+  | pseudo
+    ;
+
+class:
+    '.' IDENT {
+        $$ = vlc_css_selector_New( SPECIFIER_CLASS, $2 );
+        free( $2 );
+    }
+  ;
+
+attr_name:
+    IDENT maybe_space {
+        $$ = $1;
+    }
+    ;
+
+attrib:
+    '[' maybe_space attr_name ']' {
+        $$ = vlc_css_selector_New( SPECIFIER_ATTRIB, $3 );
+        free( $3 );
+    }
+    | '[' maybe_space attr_name match maybe_space ident_or_string maybe_space ']' {
+        $$ = vlc_css_selector_New( SPECIFIER_ATTRIB, $3 );
+        if( $$ )
+        {
+            $$->match = $4;
+            $$->p_matchsel = vlc_css_selector_New( SPECIFIER_ID, $6 );
+        }
+        free( $3 );
+        free( $6 );
+    }
+  ;
+
+match:
+    '=' {
+        $$ = MATCH_EQUALS;
+    }
+    | INCLUDES {
+        $$ = MATCH_INCLUDES;
+    }
+    | DASHMATCH {
+        $$ = MATCH_DASHMATCH;
+    }
+    | BEGINSWITH {
+        $$ = MATCH_BEGINSWITH;
+    }
+    | ENDSWITH {
+        $$ = MATCH_ENDSWITH;
+    }
+    | CONTAINS {
+        $$ = MATCH_CONTAINS;
+    }
+    ;
+
+ident_or_string:
+    IDENT
+  | STRING
+    ;
+
+pseudo:
+    ':' IDENT {
+        $$ = vlc_css_selector_New( SELECTOR_PSEUDOCLASS, $2 );
+        free( $2 );
+    }
+    | ':' ':' IDENT {
+        $$ = vlc_css_selector_New( SELECTOR_PSEUDOELEMENT, $3 );
+        free( $3 );
+    }
+    // used by :nth-*
+    | ':' FUNCTION maybe_space maybe_unary_operator NUMBER maybe_space ')' {
+        if(*$2 != 0)
+            $2[strlen($2) - 1] = 0;
+        $$ = vlc_css_selector_New( SELECTOR_PSEUDOCLASS, $2 );
+        $5.val *= $4;
+        free( $2 );
+        vlc_css_term_Clean( $5 );
+    }
+    // required for WEBVTT weirdos cue::(::past)
+    | ':' ':' FUNCTION maybe_space selector maybe_space ')' {
+        if(*$3 != 0)
+            $3[strlen($3) - 1] = 0;
+        $$ = vlc_css_selector_New( SELECTOR_PSEUDOELEMENT, $3 );
+        free( $3 );
+        if( $$ && $5 )
+        {
+            vlc_css_selector_AddSpecifier( $$, $5 );
+            $5->combinator = RELATION_SELF;
+        }
+        else
+            vlc_css_selectors_Delete( $5 );
+    }
+    // used by :nth-*(odd/even) and :lang
+    | ':' FUNCTION maybe_space IDENT maybe_space ')' {
+        if(*$2 != 0)
+            $2[strlen($2) - 1] = 0;
+        $$ = vlc_css_selector_New( SELECTOR_PSEUDOCLASS, $2 );
+        free( $2 );
+        free( $4 );
+    }
+  ;
+
+declaration_list:
+    declaration {
+        $$ = $1;
+    }
+    | decl_list declaration {
+        $$ = $1;
+        if( $$ )
+            vlc_css_declarations_Append( $$, $2 );
+    }
+    | decl_list {
+        $$ = $1;
+    }
+    | error invalid_block_list error {
+        $$ = NULL;
+    }
+    | error {
+        $$ = NULL;
+    }
+    | decl_list error {
+        $$ = $1;
+    }
+    | decl_list invalid_block_list {
+        $$ = $1;
+    }
+    ;
+
+decl_list:
+    declaration ';' maybe_space {
+        $$ = $1;
+    }
+    | declaration invalid_block_list maybe_space {
+        vlc_css_declarations_Delete( $1 );
+        $$ = NULL;
+    }
+    | declaration invalid_block_list ';' maybe_space {
+        vlc_css_declarations_Delete( $1 );
+        $$ = NULL;
+    }
+    | error ';' maybe_space {
+        $$ = NULL;
+    }
+    | error invalid_block_list error ';' maybe_space {
+        $$ = NULL;
+    }
+    | decl_list declaration ';' maybe_space {
+        if( $1 )
+        {
+            $$ = $1;
+            vlc_css_declarations_Append( $$, $2 );
+        }
+        else $$ = $2;
+    }
+    | decl_list error ';' maybe_space {
+        $$ = $1;
+    }
+    | decl_list error invalid_block_list error ';' maybe_space {
+        $$ = $1;
+    }
+    ;
+
+declaration:
+    property ':' maybe_space expr prio {
+        $$ = vlc_css_declaration_New( $1 );
+        if( $$ )
+            $$->expr = $4;
+        else
+            vlc_css_expression_Delete( $4 );
+        free( $1 );
+    }
+    |
+    property error {
+        free( $1 );
+        $$ = NULL;
+    }
+    |
+    property ':' maybe_space error expr prio {
+        free( $1 );
+        vlc_css_expression_Delete( $5 );
+        /* The default movable type template has letter-spacing: .none;  Handle this by looking for
+        error tokens at the start of an expr, recover the expr and then treat as an error, cleaning
+        up and deleting the shifted expr.  */
+        $$ = NULL;
+    }
+    |
+    property ':' maybe_space expr prio error {
+        free( $1 );
+        vlc_css_expression_Delete( $4 );
+        /* When we encounter something like p {color: red !important fail;} we should drop the declaration */
+        $$ = NULL;
+    }
+    |
+    IMPORTANT_SYM maybe_space {
+        /* Handle this case: div { text-align: center; !important } Just reduce away the stray !important. */
+        $$ = NULL;
+    }
+    |
+    property ':' maybe_space {
+        free( $1 );
+        /* div { font-family: } Just reduce away this property with no value. */
+        $$ = NULL;
+    }
+    |
+    property ':' maybe_space error {
+        free( $1 );
+        /* if we come across rules with invalid values like this case: p { weight: *; }, just discard the rule */
+        $$ = NULL;
+    }
+    |
+    property invalid_block {
+        /* if we come across: div { color{;color:maroon} }, ignore everything within curly brackets */
+        free( $1 );
+        $$ = NULL;
+    }
+  ;
+
+property:
+    IDENT maybe_space {
+        $$ = $1;
+    }
+  ;
+
+prio:
+    IMPORTANT_SYM maybe_space { $$ = true; }
+    | /* empty */ { $$ = false; }
+  ;
+
+expr:
+    term {
+        $$ = vlc_css_expression_New( $1 );
+        if( !$$ )
+            vlc_css_term_Clean( $1 );
+    }
+    | expr operator term {
+        $$ = $1;
+        if( !$1 || !vlc_css_expression_AddTerm($1, $2, $3) )
+            vlc_css_term_Clean( $3 );
+    }
+    | expr invalid_block_list {
+        vlc_css_expression_Delete( $1 );
+        $$ = NULL;
+    }
+    | expr invalid_block_list error {
+        vlc_css_expression_Delete( $1 );
+        $$ = NULL;
+    }
+    | expr error {
+        vlc_css_expression_Delete( $1 );
+        $$ = NULL;
+    }
+  ;
+
+operator:
+    '/' maybe_space {
+        $$ = '/';
+    }
+  | ',' maybe_space {
+        $$ = ',';
+    }
+  | /* empty */ {
+        $$ = 0;
+  }
+  ;
+
+term:
+  unary_term { $$ = $1; }
+  | unary_operator unary_term {
+      $$ = $2;
+      $$.val *= $1;
+  }
+  | STRING maybe_space { $$.type = TYPE_STRING; $$.psz = $1; }
+  | IDENT maybe_space { $$.type = TYPE_IDENTIFIER; $$.psz = $1; }
+  /* We might need to actually parse the number from a dimension, but we can't just put something that uses $$.string into unary_term. */
+  | DIMEN maybe_space { $$ = $1; }
+  | unary_operator DIMEN maybe_space { $$ = $2; }
+  | URI maybe_space { $$.type = TYPE_URI; $$.psz = $1; }
+  | UNICODERANGE maybe_space { $$.type = TYPE_UNICODERANGE; $$.psz = $1; }
+  | IDSEL maybe_space { $$.type = TYPE_HEXCOLOR; $$.psz = $1; }
+  | HASH maybe_space { $$.type = TYPE_HEXCOLOR; $$.psz = $1; }
+  | '#' maybe_space { $$.type = TYPE_HEXCOLOR; $$.psz = NULL; } /* Handle error case: "color: #;" */
+  /* FIXME: according to the specs a function can have a unary_operator in front. I know no case where this makes sense */
+  | function {
+      $$ = $1;
+  }
+  | '%' maybe_space { /* Handle width: %; */
+      $$.type = TYPE_PERCENT; $$.val = 0;
+  }
+  ;
+
+unary_term:
+  NUMBER maybe_space
+  | PERCENTAGE maybe_space
+  | LENGTH maybe_space
+  | ANGLE maybe_space
+  | TIME maybe_space
+  | FREQ maybe_space
+  ;
+
+function:
+    FUNCTION maybe_space expr ')' maybe_space {
+        $$.type = TYPE_FUNCTION; $$.function = $3;
+        $$.psz = $1;
+        if(*$$.psz != 0)
+            $$.psz[strlen($$.psz) - 1] = 0;
+    } |
+    FUNCTION maybe_space expr TOKEN_EOF {
+        $$.type = TYPE_FUNCTION; $$.function = $3; $$.psz = $1;
+        if(*$$.psz != 0)
+            $$.psz[strlen($$.psz) - 1] = 0;
+    } |
+    FUNCTION maybe_space ')' maybe_space {
+        $$.type = TYPE_FUNCTION; $$.function = NULL; $$.psz = $1;
+        if(*$$.psz != 0)
+            $$.psz[strlen($$.psz) - 1] = 0;
+    } |
+    FUNCTION maybe_space error {
+        $$.type = TYPE_FUNCTION; $$.function = NULL; $$.psz = $1;
+        if(*$$.psz != 0)
+            $$.psz[strlen($$.psz) - 1] = 0;
+  }
+  ;
+
+/* error handling rules */
+
+invalid_rule:
+    error invalid_block {
+        $$ = NULL;
+    }
+
+/*
+  Seems like the two rules below are trying too much and violating
+  http://www.hixie.ch/tests/evil/mixed/csserrorhandling.html
+
+  | error ';' {
+        $$ = 0;
+    }
+  | error '}' {
+        $$ = 0;
+    }
+*/
+    ;
+
+invalid_block:
+    '{' error invalid_block_list error closing_brace
+  | '{' error closing_brace
+    ;
+
+invalid_block_list:
+    invalid_block
+  | invalid_block_list error invalid_block
+;
+
+%%
+
+#ifdef YYDEBUG
+    int yydebug=1;
+#else
+    int yydebug=0;
+#endif
diff --git a/modules/codec/webvtt/CSSLexer.l b/modules/codec/webvtt/CSSLexer.l
new file mode 100644
index 0000000000..400131782a
--- /dev/null
+++ b/modules/codec/webvtt/CSSLexer.l
@@ -0,0 +1,107 @@
+/*****************************************************************************
+ * CSSLexer.l : lexer for simplified CSS, based on W3C spec
+ *****************************************************************************
+ * Copyright (C) 2017 VideoLabs, VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+%option case-insensitive
+%option reentrant
+%option bison-bridge
+%option noyywrap
+%option nounput
+%option noinput
+%option never-interactive
+%option nostdinit
+
+%{
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <vlc_common.h>
+#include "css_parser.h"
+#include "CSSGrammar.h"
+#define VAL(a, b) { yylval->term.val = (a); yylval->term.type = TYPE_ ## b; }
+#define us_strtof strtof
+char *d;
+%}
+
+h		[0-9a-f]
+nonascii	[\200-\377]
+unicode		\\{h}{1,6}[ \t\r\n\f]?
+escape		{unicode}|\\[ -~\200-\377]
+nmstart		[a-z]|{nonascii}|{escape}
+nmchar		[a-z0-9-]|{nonascii}|{escape}
+string1		\"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\"
+string2		\'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\'
+
+ident		[-]?{nmstart}{nmchar}*
+name		{nmchar}+
+num		[0-9]+|[0-9]*"."[0-9]+
+string		{string1}|{string2}
+url		([!#$%&*-~]|{nonascii}|{escape})*
+w		[ \t\r\n\f]*
+nl		\n|\r\n|\r|\f
+range		\?{1,6}|{h}(\?{0,5}|{h}(\?{0,4}|{h}(\?{0,3}|{h}(\?{0,2}|{h}(\??|{h})))))
+
+%%
+
+[ \t\r\n\f]+		{return WHITESPACE;}
+
+\/\*[^*]*\*+([^/][^*]*\*+)*\/	/* ignore comments */
+
+"<!--"			{return CDO;}
+"-->"			{return CDC;}
+"~="			{return INCLUDES;}
+"|="			{return DASHMATCH;}
+
+{string}		{ yylval->string = vlc_css_unquotedunescaped(yytext); return STRING;}
+
+{ident}			{ yylval->string = vlc_css_unescaped(yytext); return IDENT;}
+
+"@font-face"		{return FONT_FACE_SYM;}
+
+"!{w}important"		{return IMPORTANT_SYM;}
+
+{num}em			{ VAL( us_strtof(yytext, &d), EMS ); return LENGTH;}
+{num}ex			{ VAL( atoi(yytext), EXS ); return LENGTH;}
+{num}px			{ VAL( atoi(yytext), PIXELS ); return LENGTH;}
+{num}cm			{ VAL( us_strtof(yytext, &d) * 10, MILLIMETERS ); return LENGTH;}
+{num}mm			{ VAL( atoi(yytext), MILLIMETERS ); return LENGTH;}
+{num}in			{ VAL( us_strtof(yytext, &d) * 25.4, MILLIMETERS ); return LENGTH;}
+{num}pt			{ VAL( us_strtof(yytext, &d), POINTS ); return LENGTH;}
+{num}pc			{ VAL( us_strtof(yytext, &d), POINTS ); return LENGTH;}
+{num}deg		{ VAL( us_strtof(yytext, &d), DEGREES ); return ANGLE;}
+{num}rad		{ VAL( us_strtof(yytext, &d) * 0.0174533, DEGREES ); return ANGLE;}
+{num}grad		{ VAL( us_strtof(yytext, &d) * 1.1111111, DEGREES ); return ANGLE;}
+{num}ms			{ VAL( atoi(yytext), MILLISECONDS ); return TIME;}
+{num}s			{ VAL( atoi(yytext) * 1000, MILLISECONDS ); return TIME;}
+{num}Hz			{ VAL( atoi(yytext), HERTZ ); return FREQ;}
+{num}kHz		{ VAL( atoi(yytext) * 1000, HERTZ ); return FREQ;}
+{num}{ident}	{ VAL( 0, DIMENSION ); return DIMEN;}
+{num}%			{ VAL( atoi(yytext), PERCENT ); return PERCENTAGE;}
+{num}			{ VAL( us_strtof(yytext, &d), NONE ); return NUMBER;}
+
+"url("{w}{string}{w}")"	{ yylval->string = vlc_css_unquotedunescaped(yytext); return URI;}
+"url("{w}{url}{w}")"	{ yylval->string = vlc_css_unquotedunescaped(yytext); return URI;}
+{ident}"("		{ yylval->string = vlc_css_unescaped(yytext); return FUNCTION;}
+"#"{ident}      {yylval->string = vlc_css_unescaped(yytext); return IDSEL;}
+"#"{name}       {yylval->string = vlc_css_unescaped(yytext); return HASH;}
+
+U\+{range}		{ yylval->string = strdup(yytext); return UNICODERANGE;}
+U\+{h}{1,6}-{h}{1,6}	{ yylval->string = strdup(yytext); return UNICODERANGE;}
+
+.			{return *yytext;}
+
diff --git a/modules/codec/webvtt/css_bridge.h b/modules/codec/webvtt/css_bridge.h
new file mode 100644
index 0000000000..20064eab9b
--- /dev/null
+++ b/modules/codec/webvtt/css_bridge.h
@@ -0,0 +1,27 @@
+/*****************************************************************************
+ * css_bridge.h : CSS grammar/lexer bridge
+ *****************************************************************************
+ * Copyright (C) 2017 VideoLabs, VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#define yyconst const
+typedef void* yyscan_t;
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+int yylex_init (yyscan_t* scanner);
+YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner );
+YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes, int, yyscan_t yyscanner );
+int yylex_destroy (yyscan_t yyscanner );
+void yy_delete_buffer (YY_BUFFER_STATE  b , yyscan_t yyscanner);
diff --git a/modules/codec/webvtt/css_parser.c b/modules/codec/webvtt/css_parser.c
new file mode 100644
index 0000000000..01a948b268
--- /dev/null
+++ b/modules/codec/webvtt/css_parser.c
@@ -0,0 +1,517 @@
+/*****************************************************************************
+ * css_parser.c : CSS parser
+ *****************************************************************************
+ * Copyright (C) 2017 VideoLabs, VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+
+#include "css_bridge.h"
+#include "css_parser.h"
+#include "CSSGrammar.h"
+
+#include <ctype.h>
+
+static void vlc_css_term_Debug( const vlc_css_term_t a, int depth );
+static void vlc_css_expression_Debug( const vlc_css_expr_t *p_expr, int depth );
+static void vlc_css_declarations_Debug( const vlc_css_declaration_t *p_decl, int depth );
+static void vlc_css_selectors_Debug( const vlc_css_selector_t *p_sel, int depth );
+static void vlc_css_rules_Debug( const vlc_css_rule_t *p_rule, int depth );
+
+#define CHAIN_APPEND_IMPL(n, t) CHAIN_APPEND_DECL(n ,t)\
+{\
+    t ** insert = &p_a->p_next;\
+    while( *insert ) insert = &((*insert)->p_next);\
+    *insert = p_b;\
+}
+
+void vlc_css_term_Clean( vlc_css_term_t a )
+{
+    if( a.type >= TYPE_STRING )
+        free( a.psz );
+
+    if( a.type == TYPE_FUNCTION )
+    {
+        if( a.function )
+            vlc_css_expression_Delete( a.function );
+    }
+}
+
+static void vlc_css_term_Debug( const vlc_css_term_t a, int depth )
+{
+    for(int i=0;i<depth;i++) printf(" ");
+    printf("term: ");
+    if( a.type >= TYPE_STRING )
+    {
+        printf("%x %s\n", a.type, a.psz);
+        if( a.type == TYPE_FUNCTION && a.function )
+            vlc_css_expression_Debug( a.function, depth + 1 );
+    }
+    else printf("%x %f\n", a.type, a.val);
+}
+
+bool vlc_css_expression_AddTerm( vlc_css_expr_t *p_expr,
+                                        char op, vlc_css_term_t a )
+{
+    if( p_expr->i_count >= p_expr->i_alloc )
+    {
+        size_t i_realloc = (p_expr->i_alloc == 0) ? 1 : p_expr->i_alloc + 4;
+        void *reac = realloc( p_expr->seq, i_realloc * sizeof(p_expr->seq[0]) );
+        if( reac )
+        {
+            p_expr->seq = reac;
+            p_expr->i_alloc = i_realloc;
+        }
+    }
+
+    if( p_expr->i_count >= p_expr->i_alloc )
+        return false;
+
+    p_expr->seq[p_expr->i_count].op = op;
+    p_expr->seq[p_expr->i_count++].term = a;
+    return true;
+}
+
+void vlc_css_expression_Delete( vlc_css_expr_t *p_expr )
+{
+    if( p_expr )
+    {
+        for(size_t i=0; i<p_expr->i_count; i++)
+            vlc_css_term_Clean( p_expr->seq[i].term );
+        free( p_expr->seq );
+    }
+    free( p_expr );
+}
+
+static void vlc_css_expression_Debug( const vlc_css_expr_t *p_expr, int depth )
+{
+    if( p_expr )
+    {
+        for(int i=0;i<depth;i++) printf(" ");
+        printf("expression: \n");
+        for(size_t i=0; i<p_expr->i_count; i++)
+            vlc_css_term_Debug( p_expr->seq[i].term, depth + 1 );
+    }
+}
+
+vlc_css_expr_t * vlc_css_expression_New( vlc_css_term_t term )
+{
+    vlc_css_expr_t *p_expr = calloc(1, sizeof(*p_expr));
+    if(!vlc_css_expression_AddTerm( p_expr, 0, term ))
+    {
+        free(p_expr);
+        p_expr = NULL;
+    }
+    return p_expr;
+}
+
+CHAIN_APPEND_IMPL(vlc_css_declarations_Append, vlc_css_declaration_t)
+
+void vlc_css_declarations_Delete( vlc_css_declaration_t *p_decl )
+{
+    while( p_decl )
+    {
+        vlc_css_declaration_t *p_next = p_decl->p_next;
+        vlc_css_expression_Delete( p_decl->expr );
+        free( p_decl->psz_property );
+        free( p_decl );
+        p_decl = p_next;
+    }
+}
+
+static void vlc_css_declarations_Debug( const vlc_css_declaration_t *p_decl, int depth )
+{
+    while( p_decl )
+    {
+        for(int i=0;i<depth;i++) printf(" ");
+        printf("declaration: %s\n", p_decl->psz_property );
+        vlc_css_expression_Debug( p_decl->expr, depth + 1 );
+        p_decl = p_decl->p_next;
+    }
+}
+
+vlc_css_declaration_t * vlc_css_declaration_New( const char *psz )
+{
+    vlc_css_declaration_t *p_decl = calloc(1, sizeof(*p_decl));
+    p_decl->psz_property = strdup(psz);
+    return p_decl;
+}
+
+CHAIN_APPEND_IMPL(vlc_css_selector_Append, vlc_css_selector_t)
+
+void
+vlc_css_selector_AddSpecifier( vlc_css_selector_t *p_sel, vlc_css_selector_t *p_spec )
+{
+    *p_sel->specifiers.pp_append = p_spec;
+    while(p_spec)
+    {
+        p_sel->specifiers.pp_append = &p_spec->p_next;
+        p_spec = p_spec->p_next;
+    }
+}
+
+void vlc_css_selectors_Delete( vlc_css_selector_t *p_sel )
+{
+    while( p_sel )
+    {
+        vlc_css_selector_t *p_next = p_sel->p_next;
+        free( p_sel->psz_name );
+        vlc_css_selectors_Delete( p_sel->specifiers.p_first );
+        vlc_css_selectors_Delete( p_sel->p_matchsel );
+        free( p_sel );
+        p_sel = p_next;
+    }
+}
+
+static void vlc_css_selectors_Debug( const vlc_css_selector_t *p_sel, int depth )
+{
+    while( p_sel )
+    {
+        for(int i=0;i<depth;i++) printf(" "); printf("selector %c%s:\n", p_sel->combinator, p_sel->psz_name );
+        vlc_css_selectors_Debug( p_sel->p_matchsel, depth + 1 );
+        vlc_css_selectors_Debug( p_sel->specifiers.p_first, depth + 1 );
+        p_sel = p_sel->p_next;
+    }
+}
+
+vlc_css_selector_t * vlc_css_selector_New( int type, const char *psz )
+{
+    vlc_css_selector_t *p_sel = calloc(1, sizeof(*p_sel));
+    p_sel->psz_name = strdup(psz);
+    p_sel->type = type;
+    p_sel->combinator = RELATION_SELF;
+    p_sel->specifiers.pp_append = &p_sel->specifiers.p_first;
+    return p_sel;
+}
+
+void vlc_css_rules_Delete( vlc_css_rule_t *p_rule )
+{
+    while(p_rule)
+    {
+        vlc_css_rule_t *p_next = p_rule->p_next;
+        vlc_css_selectors_Delete( p_rule->p_selectors );
+        vlc_css_declarations_Delete( p_rule->p_declarations );
+        free(p_rule);
+        p_rule = p_next;
+    }
+}
+
+static void vlc_css_rules_Debug( const vlc_css_rule_t *p_rule, int depth )
+{
+    int j = 0;
+    while(p_rule)
+    {
+        for(int i=0;i<depth;i++) printf(" "); printf("rule %d:\n", j++);
+        vlc_css_selectors_Debug( p_rule->p_selectors, depth + 1 );
+        vlc_css_declarations_Debug( p_rule->p_declarations, depth + 1 );
+        p_rule = p_rule->p_next;
+    }
+}
+
+vlc_css_rule_t * vlc_css_rule_New( void )
+{
+    vlc_css_rule_t *p_rule = calloc(1, sizeof(*p_rule));
+    return p_rule;
+}
+
+void vlc_css_parser_AddRule( vlc_css_parser_t *p_parser,
+                                           vlc_css_rule_t *p_rule )
+{
+    (*p_parser->rules.pp_append) = p_rule;
+    p_parser->rules.pp_append = &p_rule->p_next;
+}
+
+void vlc_css_parser_Debug( const vlc_css_parser_t *p_parser )
+{
+    vlc_css_rules_Debug( p_parser->rules.p_first, 0 );
+}
+
+void vlc_css_parser_Clean( vlc_css_parser_t *p_parser )
+{
+    vlc_css_rules_Delete( p_parser->rules.p_first );
+}
+
+void vlc_css_parser_Init( vlc_css_parser_t *p_parser )
+{
+    memset(p_parser, 0, sizeof(vlc_css_parser_t));
+    p_parser->rules.pp_append = &p_parser->rules.p_first;
+}
+
+bool vlc_css_parser_ParseBytes( vlc_css_parser_t *p_parser, const uint8_t *p_data, size_t i_data )
+{
+    yyscan_t yy;
+    yylex_init(&yy);
+
+    YY_BUFFER_STATE buf = yy_scan_bytes( (const char*) p_data, i_data, yy );
+
+    bool b_ret = !yyparse( yy, p_parser );
+
+    yy_delete_buffer( buf, yy );
+    yylex_destroy( yy );
+
+    return b_ret;
+}
+
+bool vlc_css_parser_ParseString( vlc_css_parser_t *p_parser, const char *psz_css )
+{
+    yyscan_t yy;
+    yylex_init(&yy);
+
+    YY_BUFFER_STATE buf = yy_scan_string( psz_css, yy );
+
+    bool b_ret = !yyparse( yy, p_parser );
+
+    yy_delete_buffer( buf, yy );
+    yylex_destroy( yy );
+
+    return b_ret;
+}
+
+static int CodePointToUTF8( uint32_t ucs4, char *p )
+{
+    /* adapted from codepoint conversion from strings.h */
+    if( ucs4 <= 0x7F )
+    {
+        p[0] = ucs4;
+        return 1;
+    }
+    else if( ucs4 <= 0x7FF )
+    {
+        p[0] = 0xC0 |  (ucs4 >>  6);
+        p[1] = 0x80 |  (ucs4        & 0x3F);
+        return 2;
+    }
+    else if( ucs4 <= 0xFFFF )
+    {
+        p[0] = 0xE0 |  (ucs4 >> 12);
+        p[1] = 0x80 | ((ucs4 >>  6) & 0x3F);
+        p[2] = 0x80 |  (ucs4        & 0x3F);
+        return 3;
+    }
+    else if( ucs4 <= 0x1FFFFF )
+    {
+        p[0] = 0xF0 |  (ucs4 >> 18);
+        p[1] = 0x80 | ((ucs4 >> 12) & 0x3F);
+        p[2] = 0x80 | ((ucs4 >>  6) & 0x3F);
+        p[3] = 0x80 |  (ucs4        & 0x3F);
+        return 4;
+    }
+    else if( ucs4 <= 0x3FFFFFF )
+    {
+        p[0] = 0xF8 |  (ucs4 >> 24);
+        p[1] = 0x80 | ((ucs4 >> 18) & 0x3F);
+        p[2] = 0x80 | ((ucs4 >> 12) & 0x3F);
+        p[3] = 0x80 | ((ucs4 >>  6) & 0x3F);
+        p[4] = 0x80 |  (ucs4        & 0x3F);
+        return 5;
+    }
+    else
+    {
+        p[0] = 0xFC |  (ucs4 >> 30);
+        p[1] = 0x80 | ((ucs4 >> 24) & 0x3F);
+        p[2] = 0x80 | ((ucs4 >> 18) & 0x3F);
+        p[3] = 0x80 | ((ucs4 >> 12) & 0x3F);
+        p[4] = 0x80 | ((ucs4 >>  6) & 0x3F);
+        p[5] = 0x80 |  (ucs4        & 0x3F);
+        return 6;
+    }
+}
+
+void vlc_css_unescape( char *psz )
+{
+    if( !psz )
+        return;
+    char *r = psz;
+    char *w = psz;
+
+    while( *r )
+    {
+        if( *r == '\\' )
+        {
+            r++;
+            /* newlines */
+            if( *r == 0 )
+            {
+                break;
+            }
+            else if( strchr( "nfr", *r ) )
+            {
+                switch( r[0] )
+                {
+                    case 'n':
+                        *w++ = '\n';
+                        r++;
+                        break;
+                    case 'r':
+                        *w++ = '\r';
+                        if( r[1] && r[1] == 'n' )
+                        {
+                            *w++ = '\n';
+                            r++;
+                        }
+                        r++;
+                        break;
+                    case 'f':
+                        *w++ = '\f';
+                        r++;
+                        break;
+                }
+            }
+            else if( isxdigit( *r ) )
+            {
+                const char *p_start = r;
+                int i;
+                for( i=0; i<6 && *r && isxdigit( *r ); i++ )
+                    r++;
+                const char backup = *r;
+                *r = 0;
+                unsigned i_value = strtoul( p_start, NULL, 16 );
+                *r = backup;
+                if( i < 6 && *r && *r == ' ' )
+                    r++;
+                w += CodePointToUTF8( i_value, w );
+            }
+        }
+        else
+        {
+            *w++ = *r++;
+        }
+    }
+
+    *w = 0;
+}
+
+char * vlc_css_unescaped( const char *psz )
+{
+    char *psz_ret = strdup( psz );
+    vlc_css_unescape( psz_ret );
+    return psz_ret;
+}
+
+char * vlc_css_unquoted( const char *psz )
+{
+    char *psz_ret;
+    if( *psz == '\'' || *psz == '\"' )
+    {
+        size_t i_len = strlen(psz);
+        if( psz[i_len - 1] == psz[0] )
+            psz_ret = strndup( psz + 1, i_len - 2 );
+        else
+            psz_ret = strdup( psz );
+    }
+    else
+    {
+        psz_ret = strdup( psz );
+    }
+    return psz_ret;
+}
+
+
+char * vlc_css_unquotedunescaped( const char *psz )
+{
+    char *psz_ret = vlc_css_unquoted( psz );
+    if( psz_ret )
+        vlc_css_unescape( psz_ret );
+    return psz_ret;
+}
+
+#ifdef CSS_PARSER_DEBUG
+
+
+static void css_properties_Debug( const vlc_css_declaration_t *p_decl )
+{
+    printf("set %s to ", p_decl->psz_property);
+    for( size_t i=0; i<p_decl->expr->i_count; i++ )
+    {
+        printf("term %s ", p_decl->expr->seq[i].term.psz);
+    }
+    printf("\n");
+}
+
+void css_selector_Debug( const vlc_css_selector_t *p_sel )
+{
+    printf("select its ");
+    switch( p_sel->combinator )
+    {
+        case RELATION_DESCENDENT:
+            printf("descendent");
+            break;
+        case RELATION_DIRECTADJACENT:
+            printf("adjacent");
+            break;
+        case RELATION_INDIRECTADJACENT:
+            printf("indirect adjacent");
+            break;
+        case RELATION_CHILD:
+            printf("child");
+            break;
+        case RELATION_SELF:
+            break;
+    }
+
+    printf(" nodes matching filter: ");
+    switch( p_sel->type )
+    {
+        case SELECTOR_SIMPLE:
+            printf("<%s>\n", p_sel->psz_name);
+            break;
+        case SELECTOR_PSEUDOCLASS:
+            printf(":%s\n", p_sel->psz_name);
+            break;
+        case SELECTOR_PSEUDOELEMENT:
+            printf("::%s\n", p_sel->psz_name);
+            break;
+        case SPECIFIER_ID:
+            printf("%s\n", p_sel->psz_name);
+            break;
+        case SPECIFIER_CLASS:
+            printf(".%s\n", p_sel->psz_name);
+            break;
+        case SPECIFIER_ATTRIB:
+            printf("[%s]\n", p_sel->psz_name);
+            break;
+    }
+}
+
+void css_rule_Debug( const vlc_css_rule_t *p_rule )
+{
+    if( p_rule == NULL )
+    return;
+    printf("add for rule nodes:\n");
+    for( const vlc_css_selector_t *p_sel = p_rule->p_selectors;
+                                   p_sel; p_sel = p_sel->p_next )
+    {
+        css_selector_Debug( p_sel );
+        for( const vlc_css_selector_t *p_spec = p_sel->specifiers.p_first;
+                                       p_spec; p_spec = p_spec->p_next )
+            css_selector_Debug( p_spec );
+
+        if( p_sel->p_next )
+            printf("add nodes\n");
+    }
+
+    for( const vlc_css_declaration_t *p_decl = p_rule->p_declarations;
+                                      p_decl; p_decl = p_decl->p_next )
+    {
+        css_properties_Debug( p_decl );
+    }
+}
+
+#endif
diff --git a/modules/codec/webvtt/css_parser.h b/modules/codec/webvtt/css_parser.h
new file mode 100644
index 0000000000..4df5bc1fc4
--- /dev/null
+++ b/modules/codec/webvtt/css_parser.h
@@ -0,0 +1,182 @@
+/*****************************************************************************
+ * css_parser.h : CSS parser interface
+ *****************************************************************************
+ * Copyright (C) 2017 VideoLabs, VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef CSS_PARSER_H
+#define CSS_PARSER_H
+
+//#define YYDEBUG 1
+//#define CSS_PARSER_DEBUG
+
+typedef struct vlc_css_parser_t vlc_css_parser_t;
+typedef struct vlc_css_selector_t vlc_css_selector_t;
+typedef struct vlc_css_declaration_t vlc_css_declaration_t;
+typedef struct vlc_css_rule_t vlc_css_rule_t;
+typedef struct vlc_css_expr_t vlc_css_expr_t;
+
+typedef struct
+{
+    float val;
+    char *psz;
+    vlc_css_expr_t *function;
+    enum
+    {
+        TYPE_NONE = 0,
+        TYPE_EMS,
+        TYPE_EXS,
+        TYPE_PIXELS,
+        TYPE_POINTS,
+        TYPE_MILLIMETERS,
+        TYPE_PERCENT,
+        TYPE_MILLISECONDS,
+        TYPE_HERTZ,
+        TYPE_DEGREES,
+        TYPE_DIMENSION,
+        TYPE_STRING = 0x20,
+        TYPE_FUNCTION,
+        TYPE_IDENTIFIER,
+        TYPE_HEXCOLOR,
+        TYPE_UNICODERANGE,
+        TYPE_URI,
+    } type;
+} vlc_css_term_t;
+
+struct vlc_css_expr_t
+{
+    struct
+    {
+        char op;
+        vlc_css_term_t term;
+    } *seq;
+    size_t i_alloc;
+    size_t i_count;
+};
+
+struct vlc_css_declaration_t
+{
+    char *psz_property;
+    vlc_css_expr_t *expr;
+    vlc_css_declaration_t *p_next;
+};
+
+enum vlc_css_match_e
+{
+    MATCH_EQUALS,
+    MATCH_INCLUDES,
+    MATCH_DASHMATCH,
+    MATCH_BEGINSWITH,
+    MATCH_ENDSWITH,
+    MATCH_CONTAINS,
+};
+
+enum vlc_css_relation_e
+{
+   RELATION_SELF = 0,
+   RELATION_DESCENDENT = ' ',
+   RELATION_DIRECTADJACENT = '+',
+   RELATION_INDIRECTADJACENT = '~',
+   RELATION_CHILD = '>',
+};
+
+struct vlc_css_selector_t
+{
+    char *psz_name;
+    enum
+    {
+        SELECTOR_SIMPLE = 0,
+        SELECTOR_PSEUDOCLASS,
+        SELECTOR_PSEUDOELEMENT,
+/*        SELECTOR_PSEUDONTHCHILD,
+        SELECTOR_PSEUDONTHTYPE,
+        SELECTOR_PSEUDONTHLASTCHILD,
+        SELECTOR_PSEUDONTHLASTTYPE,*/
+        SPECIFIER_ID,
+        SPECIFIER_CLASS,
+        SPECIFIER_ATTRIB,
+    } type;
+    struct
+    {
+        vlc_css_selector_t *p_first;
+        vlc_css_selector_t **pp_append;
+    } specifiers;
+
+    enum vlc_css_match_e match;
+    vlc_css_selector_t *p_matchsel;
+
+    enum vlc_css_relation_e combinator;
+    vlc_css_selector_t *p_next;
+};
+
+struct vlc_css_rule_t
+{
+    bool b_valid;
+    vlc_css_selector_t *p_selectors;
+    vlc_css_declaration_t *p_declarations;
+
+    vlc_css_rule_t *p_next;
+};
+
+struct vlc_css_parser_t
+{
+    struct
+    {
+        vlc_css_rule_t *p_first;
+        vlc_css_rule_t **pp_append;
+    } rules;
+};
+
+#define CHAIN_APPEND_DECL(n, t) void n( t *p_a, t *p_b )
+
+void vlc_css_term_Clean( vlc_css_term_t a );
+bool vlc_css_expression_AddTerm( vlc_css_expr_t *p_expr, char op, vlc_css_term_t a );
+void vlc_css_expression_Delete( vlc_css_expr_t *p_expr );
+vlc_css_expr_t * vlc_css_expression_New( vlc_css_term_t term );
+
+CHAIN_APPEND_DECL(vlc_css_declarations_Append, vlc_css_declaration_t);
+void vlc_css_declarations_Delete( vlc_css_declaration_t *p_decl );
+vlc_css_declaration_t * vlc_css_declaration_New( const char *psz );
+
+CHAIN_APPEND_DECL(vlc_css_selector_Append, vlc_css_selector_t);
+void vlc_css_selector_AddSpecifier( vlc_css_selector_t *p_sel, vlc_css_selector_t *p_spec );
+void vlc_css_selectors_Delete( vlc_css_selector_t *p_sel );
+vlc_css_selector_t * vlc_css_selector_New( int type, const char *psz );
+
+void vlc_css_rules_Delete( vlc_css_rule_t *p_rule );
+vlc_css_rule_t * vlc_css_rule_New( void );
+
+void vlc_css_parser_AddRule( vlc_css_parser_t *p_parser, vlc_css_rule_t *p_rule );
+void vlc_css_parser_Debug( const vlc_css_parser_t *p_parser );
+void vlc_css_parser_Clean( vlc_css_parser_t *p_parser );
+void vlc_css_parser_Init( vlc_css_parser_t *p_parser );
+
+bool vlc_css_parser_ParseBytes( vlc_css_parser_t *p_parser, const uint8_t *, size_t );
+bool vlc_css_parser_ParseString( vlc_css_parser_t *p_parser, const char * );
+
+void vlc_css_unescape( char *psz );
+char * vlc_css_unquoted( const char *psz );
+char * vlc_css_unescaped( const char *psz );
+char * vlc_css_unquotedunescaped( const char *psz );
+
+#   ifdef CSS_PARSER_DEBUG
+
+void css_selector_Debug( const vlc_css_selector_t *p_sel );
+void css_rule_Debug( const vlc_css_rule_t *p_rule );
+
+#   endif
+
+#endif




More information about the vlc-commits mailing list