From 649b3d02f50855953cd19b5bdbfee177ffba38d3 Mon Sep 17 00:00:00 2001 From: Francois Cartegnie Date: Mon, 9 Oct 2017 11:27:15 +0200 Subject: [PATCH] codec: webvtt: add CSS lexer/grammar/parser --- 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(+) create mode 100644 modules/codec/webvtt/CSSGrammar.y create mode 100644 modules/codec/webvtt/CSSLexer.l create mode 100644 modules/codec/webvtt/css_bridge.h create mode 100644 modules/codec/webvtt/css_parser.c create mode 100644 modules/codec/webvtt/css_parser.h 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@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2008 Eric Seidel + * 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 +#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 +%right IDENT + +%nonassoc IDSEL +%nonassoc HASH +%nonassoc ':' +%nonassoc '.' +%nonassoc '[' +%nonassoc '*' +%nonassoc error +%left '|' + +%token FONT_FACE_SYM +%token CHARSET_SYM + +%token IMPORTANT_SYM + +%token CDO +%token CDC +%token LENGTH +%token ANGLE +%token TIME +%token FREQ +%token DIMEN +%token PERCENTAGE +%token NUMBER + +%destructor { vlc_css_term_Clean($$); } + +%token URI +%token FUNCTION +%token UNICODERANGE + +%type combinator + +%type charset +%type ignored_charset +%type ruleset +%type font_face +%type invalid_rule +%type rule +%type valid_rule +%destructor { vlc_css_rules_Delete($$); } + +%type ident_or_string +%type property + +%type specifier +%type specifier_list +%type simple_selector +%type selector +%type selector_list +%type selector_with_trailing_whitespace +%type class +%type attrib +%type pseudo +%destructor { vlc_css_selectors_Delete($$); } + +%type declaration_list +%type decl_list +%type declaration +%destructor { vlc_css_declarations_Delete($$); } + +%type prio + +%type match +%type unary_operator +%type maybe_unary_operator +%type operator + +%type expr +%type term +%type unary_term +%type function +%destructor { vlc_css_expression_Delete($$); } + +%type element_name +%type attr_name + +%destructor { free($$); } + +%% + +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 +#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 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 + +#include "css_bridge.h" +#include "css_parser.h" +#include "CSSGrammar.h" + +#include + +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= 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; ii_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;ii_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;ipsz_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;icombinator, 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;ip_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; iexpr->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