Changeset 6970 in josm for trunk


Ignore:
Timestamp:
2014-04-12T00:30:32+02:00 (6 years ago)
Author:
bastiK
Message:

mapcss: proper @media support
no longer chokes on unkown grammar when it is "commented out" by an @media section
2 pass parser needed

Location:
trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java

    r6883 r6970  
    376376                return OsmPrimitive.getFilteredList(e.osm.getReferrers(), Way.class).isEmpty();
    377377            }
    378             return true;
     378            return false;
    379379        }
    380380
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj

    r6927 r6970  
    88package org.openstreetmap.josm.gui.mappaint.mapcss.parsergen;
    99
    10 import java.awt.Color;
     10import java.io.InputStream;
    1111import java.util.ArrayList;
    1212import java.util.List;
     
    2828import org.openstreetmap.josm.tools.ColorHelper;
    2929import org.openstreetmap.josm.tools.Pair;
    30 import org.openstreetmap.josm.tools.Utils;
    3130import org.openstreetmap.josm.Main;
    3231
    33 /*************
    34  * <pre>
    35  * Parser definitions
    36  *
    37  *                       rule
    38  *  _______________________|______________________________
    39  * |                                                      |
    40  *        selector                      declaration
    41  *  _________|___________________   _________|____________
    42  * |                             | |                      |
    43  *
    44  * way|z11-12[highway=residential] { color: red; width: 3 }
    45  *
    46  *    |_____||___________________|   |_________|
    47  *       |            |                   |
    48  *     zoom       condition          instruction
    49  *
    50  * more general:
    51  *
    52  * way|z13-[a=b][c=d]::subpart, way|z-3[u=v]:closed::subpart2 { p1 : val; p2 : val; }
    53  *
    54  * 'val' can be a literal, or an expression like "prop(width, default) + 0.8".
    55  *
    56  * {@literal @media} { ... } queries are supported, following http://www.w3.org/TR/css3-mediaqueries/#syntax
    57  *
    58  *                               media_query
    59  *         ___________________________|_______________________________
    60  *        |                                                           |
    61  * {@literal @media} all and (min-josm-version: 7789) and (max-josm-version: 7790), all and (user-agent: xyz) { ... }
    62  *                |______________________|
    63  *                          |
    64  *                    media_expression
    65  * </pre>
     32/**
     33 * MapCSS parser.
     34 *
     35 * Contains two independent grammars:
     36 * (a) the preprocessor and (b) the main mapcss parser.
     37 *
     38 * The preprocessor handles @media syntax. Basically this allows
     39 * to write one style for different versions of JOSM (or different editors).
     40 * When the @media condition is not fulfilled, it should simply skip over
     41 * the whole section and not attempt to parse the possibly unknown
     42 * grammar. It preserves whitespace and comments, in order to keep the
     43 * line and column numbers in the error messages correct for the second pass.
     44 *
    6645 */
    6746
    6847public class MapCSSParser {
    6948    MapCSSStyleSource sheet;
     49    StringBuilder sb;
     50
     51    /**
     52     * Nicer way to refer to a lexical state.
     53     */
     54    public static enum LexicalState {
     55        PREPROCESSOR(0), /* the preprocessor */
     56        DEFAULT(2);      /* the main parser */
     57       
     58        int idx; // the integer, which javacc assigns to this state
     59       
     60        LexicalState(int idx) {
     61            if (!this.name().equals(MapCSSParserTokenManager.lexStateNames[idx])) {
     62                throw new RuntimeException();
     63            }
     64            this.idx = idx;
     65        }
     66    };
     67   
     68    /**
     69     * Constructor which initializes the parser with a certain lexical state.
     70     */
     71    public MapCSSParser(InputStream in, String encoding, LexicalState initState) {
     72        this(createTokenManager(in, encoding, initState));
     73    }
     74
     75    protected static MapCSSParserTokenManager createTokenManager(InputStream in, String encoding, LexicalState initState) {
     76        SimpleCharStream scs;
     77        try {
     78            scs = new SimpleCharStream(in, encoding, 1, 1);
     79        } catch(java.io.UnsupportedEncodingException e) {
     80            throw new RuntimeException(e);
     81        }
     82        return new MapCSSParserTokenManager(scs, initState.idx);
     83    }
    7084}
    7185PARSER_END(MapCSSParser)
     
    7387/*************
    7488 * Token definitions
    75  */
     89 *
     90 * Lexical states for the preprocessor: <PREPROCESSOR>, <PP_COMMENT>
     91 * Lexical states for the main parser: <DEFAULT>, <COMMENT>
     92 */
     93 
     94<PREPROCESSOR>
     95TOKEN:
     96{
     97    < PP_AND: "and" >
     98|   < PP_NOT: "not" >
     99|   < PP_MEDIA: "@media" >
     100|   < PP_NEWLINECHAR: "\n" | "\r" | "\f" >
     101|   < PP_WHITESPACE: " " | "\t" >
     102|   < PP_COMMENT_START: "/*" > : PP_COMMENT
     103}
     104
     105<PP_COMMENT>
     106TOKEN:
     107{
     108    < PP_COMMENT_END: "*/" > : PREPROCESSOR
     109}
     110
     111<PP_COMMENT>
     112MORE:
     113{
     114    < ~[] >
     115}
    76116
    77117<DEFAULT>
    78118TOKEN [IGNORE_CASE]:
    79119{
    80  /* Special keywords in some contexts, ordinary identifiers in other contexts.
    81     Don't use the javacc token contexts (except DEFAULT and COMMENT) because
    82     they complicate error handling. Instead simply add a parsing rule <code>ident()</code>
    83     that parses the IDENT token and in addition all the keyword tokens. */
    84     < AND: "and" >
    85 |   < SET: "set" >
    86 |   < NOT: "not" >
    87 |   < MEDIA: "@media" >
    88 }
    89 
    90 <DEFAULT>
     120 /* Special keyword in some contexts, ordinary identifier in other contexts.
     121    Use the parsing rule <code>ident()</code> to refer to a general
     122    identifier, including "set". */
     123   < SET: "set" >
     124}
     125
     126<DEFAULT,PREPROCESSOR>
    91127TOKEN:
    92128{
    93129    < IDENT: ["a"-"z","A"-"Z","_"] ( ["a"-"z","A"-"Z","_","-","0"-"9"] )* >
    94130|   < UINT: ["1"-"9"] ( ["0"-"9"] )* >
    95 |   < UFLOAT: ( ["0"-"9"] )+ ( "." ( ["0"-"9"] )+ )? >
    96131|   < STRING: "\"" ( [" ","!","#"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\\"" | "\\\\" )*  "\"" >
    97132|   < #PREDEFINED: "\\" ["d","D","s","S","w","W"] >
    98133|   < #REGEX_CHAR_WITHOUT_STAR: [" "-")","+"-".","0"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\/" | "\\\\" | "\\[" | "\\]" | "\\+" | "\\." | "\\'" | "\\\"" | <PREDEFINED> >
    99134|   < REGEX: "/" <REGEX_CHAR_WITHOUT_STAR> ( <REGEX_CHAR_WITHOUT_STAR> | "*" )*  "/" >
     135|   < LBRACE: "{" >
     136|   < RBRACE: "}" >
     137|   < LPAR: "(" >
     138|   < RPAR: ")" >
     139|   < COLON: ":" >
     140}
     141
     142<PREPROCESSOR>
     143TOKEN:
     144{
     145    < PP_SOMETHING_ELSE : ~[] >
     146}
     147
     148<DEFAULT>
     149TOKEN:
     150{
     151    < UFLOAT: ( ["0"-"9"] )+ ( "." ( ["0"-"9"] )+ )? >
    100152|   < #H: ["0"-"9","a"-"f","A"-"F"] >
    101153|   < HEXCOLOR: "#" ( <H><H><H><H><H><H><H><H> | <H><H><H><H><H><H> | <H><H><H> ) >
     
    103155|   < STAR: "*" >
    104156|   < SLASH: "/" >
    105 |   < LBRACE: "{" >
    106 |   < RBRACE: "}" >
    107157|   < LSQUARE: "[" >
    108158|   < RSQUARE: "]" >
    109 |   < LPAR: "(" >
    110 |   < RPAR: ")" >
    111159|   < GREATER_EQUAL: ">=" >
    112160|   < LESS_EQUAL: "<=" >
     
    116164|   < EXCLAMATION: "!" >
    117165|   < TILDE: "~" >
    118 |   < COLON: ":" >
    119166|   < DCOLON: "::" >
    120167|   < SEMICOLON: ";" >
     
    147194}
    148195
    149 int uint() :
    150 {
    151     Token i;
    152 }
    153 {
    154     i=<UINT> { return Integer.parseInt(i.image); }
    155 }
    156 
    157 int int_() :
    158 {
    159     int i;
    160 }
    161 {
    162     <MINUS> i=uint() { return -i; } | i=uint() { return i; }
    163 }
    164 
    165 float ufloat() :
    166 {
    167     Token f;
    168 }
    169 {
    170     ( f=<UFLOAT> | f=<UINT> )
    171     { return Float.parseFloat(f.image); }
    172 }
    173 
    174 float float_() :
    175 {
    176     float f;
    177 }
    178 {
    179     <MINUS> f=ufloat() { return -f; } | f=ufloat() { return f; }
    180 }
    181 
    182 String string() :
    183 {
    184     Token t;
    185 }
    186 {
    187     t=<STRING>
    188     { return t.image.substring(1, t.image.length() - 1).replace("\\\"", "\"").replace("\\\\", "\\"); }
    189 }
    190 
    191 String ident():
    192 {
    193     Token t;
    194     String s;
    195 }
    196 {
    197     ( t=<IDENT> | t=<SET> | t=<NOT> | t=<AND> ) { return t.image; }
    198 }
    199 
    200 String string_or_ident() :
    201 {
    202     Token t;
    203     String s;
    204 }
    205 {
    206     ( s=ident() | s=string() ) { return s; }
    207 }
    208 
    209 String regex() :
    210 {
    211     Token t;
    212 }
    213 {
    214     t=<REGEX>
    215     { return t.image.substring(1, t.image.length() - 1); }
    216 }
     196
     197/*************
     198 *
     199 * Preprocessor parser definitions:
     200 *
     201 * <pre>
     202 *
     203 * {@literal @media} { ... } queries are supported, following http://www.w3.org/TR/css3-mediaqueries/#syntax
     204 *
     205 *                               media_query
     206 *         ___________________________|_______________________________
     207 *        |                                                           |
     208 * {@literal @media} all and (min-josm-version: 7789) and (max-josm-version: 7790), all and (user-agent: xyz) { ... }
     209 *                |______________________|
     210 *                          |
     211 *                    media_expression
     212 * </pre>
     213 */
     214 
    217215
    218216/**
    219  * white-space
    220  */
    221 void s() :
    222 {
    223 }
    224 {
    225     ( <S> )?
     217 * root method for the preprocessor.
     218 */
     219String pp_root(MapCSSStyleSource sheet):
     220{
     221}
     222{
     223    { sb = new StringBuilder(); this.sheet = sheet; }
     224    pp_black_box(true) <EOF>
     225    { return sb.toString(); }
    226226}
    227227
    228228/**
    229  * mix of white-space and comments
    230  */
    231 void w() :
    232 {
    233 }
    234 {
    235     ( <S> | <COMMENT_START> <COMMENT_END> )*
    236 }
    237 
    238 /**
    239  * comma delimited list of floats (at least 2, all &gt;= 0)
    240  */
    241 List<Float> float_array() :
    242 {
    243     float f;
    244     List<Float> fs = new ArrayList<Float>();
    245 }
    246 {
    247     f=ufloat() { fs.add(f); }
    248     (
    249         <COMMA> s()
    250         f=ufloat() { fs.add(f); }
    251     )+
    252     {
    253         return fs;
    254     }
    255 }
    256 
    257 /**
    258  * root
    259  */
    260 void sheet(MapCSSStyleSource sheet):
    261 {
    262     MapCSSRule r;
    263 }
    264 {
    265     { this.sheet = sheet; }
    266     w()
    267     rules(true) ( media() w() rules(true) )*
    268     <EOF>
    269 }
    270 
    271 void media():
     229 * Parse any unknown grammar (black box).
     230 *
     231 * Only stop when "@media" is encountered and keep track of correct number of
     232 * opening and closing curly brackets.
     233 *
     234 * @param write false if this content should be skipped (@pp_media condition is not fulfilled), true otherwise
     235 */
     236void pp_black_box(boolean write):
     237{
     238    Token t;
     239}
     240{
     241    (
     242        (t=<PP_AND> | t=<PP_NOT> | t=<UINT> | t=<STRING> | t=<REGEX> | t=<LPAR> | t=<RPAR> | t=<COLON> | t=<IDENT> | t=<PP_SOMETHING_ELSE>) { if (write) sb.append(t.image); }
     243        |
     244            pp_w1()
     245        |
     246            pp_media()
     247        |
     248            t=<LBRACE> { if (write) sb.append(t.image); } pp_black_box(write) t=<RBRACE> { if (write) sb.append(t.image); }
     249    )*
     250}
     251
     252void pp_media():
    272253{
    273254    boolean pass = false;
     
    276257}
    277258{
    278     <MEDIA> w()
    279     ( q=media_query() { pass = pass || q; empty = false; }
    280         ( <COMMA> w() q=media_query() { pass = pass || q; } )*
     259    <PP_MEDIA> pp_w()
     260    ( q=pp_media_query() { pass = pass || q; empty = false; }
     261        ( <COMMA> pp_w() q=pp_media_query() { pass = pass || q; } )*
    281262    )?
    282     <LBRACE> w()
    283     rules(empty || pass)
     263    <LBRACE>
     264    pp_black_box(empty || pass)
    284265    <RBRACE>
    285266}
    286267
    287 boolean media_query():
     268boolean pp_media_query():
    288269{
    289270    Token t;
     
    294275}
    295276{
    296     ( <NOT> { invert = true; } w() )?
    297     (
    298             t=<IDENT> { mediatype = t.image.toLowerCase(); } w()
    299             ( <AND> w() e=media_expression() { pass = pass && e; } w() )*
    300         |
    301             e=media_expression() { pass = pass && e; } w()
    302             ( <AND> w() e=media_expression() { pass = pass && e; } w() )*
     277    ( <PP_NOT> { invert = true; } pp_w() )?
     278    (
     279            t=<IDENT> { mediatype = t.image.toLowerCase(); } pp_w()
     280            ( <PP_AND> pp_w() e=pp_media_expression() { pass = pass && e; } pp_w() )*
     281        |
     282            e=pp_media_expression() { pass = pass && e; } pp_w()
     283            ( <PP_AND> pp_w() e=pp_media_expression() { pass = pass && e; } pp_w() )*
    303284    )
    304285    {
     
    310291}
    311292
    312 boolean media_expression():
     293/**
     294 * Parse an @media expression.
     295 *
     296 * The parsing rule {@link #literal()} from the main mapcss parser is reused here.
     297 *
     298 * @return true if the condition is fulfilled
     299 */
     300boolean pp_media_expression():
    313301{
    314302    Token t;
     
    317305}
    318306{
    319     <LPAR> w() t=<IDENT> { feature = t.image; } w() ( <COLON> w() val=literal() )? <RPAR>
     307    <LPAR> pp_w() t=<IDENT> { feature = t.image; } pp_w() ( <COLON> pp_w() val=literal() )? <RPAR>
    320308    { return this.sheet.evalMediaExpression(feature, val); }
    321309}
    322310
    323 void rules(boolean add):
     311void pp_w1():
     312{
     313    Token t;
     314}
     315{
     316    t=<PP_NEWLINECHAR> { sb.append(t.image); }
     317        |
     318    t=<PP_WHITESPACE> { sb.append(t.image); }
     319        |
     320    t=<PP_COMMENT_START> { sb.append(t.image); } t=<PP_COMMENT_END> { sb.append(t.image); }
     321}
     322
     323void pp_w():
     324{
     325}
     326{
     327 ( pp_w1() )*
     328}
     329
     330/*************
     331 *
     332 * Parser definition for the main MapCSS parser:
     333 *
     334 * <pre>
     335 *
     336 *                       rule
     337 *  _______________________|______________________________
     338 * |                                                      |
     339 *        selector                      declaration
     340 *  _________|___________________   _________|____________
     341 * |                             | |                      |
     342 *
     343 * way|z11-12[highway=residential] { color: red; width: 3 }
     344 *
     345 *    |_____||___________________|   |_________|
     346 *       |            |                   |
     347 *     zoom       condition          instruction
     348 *
     349 * more general:
     350 *
     351 * way|z13-[a=b][c=d]::subpart, way|z-3[u=v]:closed::subpart2 { p1 : val; p2 : val; }
     352 *
     353 * 'val' can be a literal, or an expression like "prop(width, default) + 0.8".
     354 *
     355 * </pre>
     356 */
     357
     358int uint() :
     359{
     360    Token i;
     361}
     362{
     363    i=<UINT> { return Integer.parseInt(i.image); }
     364}
     365
     366int int_() :
     367{
     368    int i;
     369}
     370{
     371    <MINUS> i=uint() { return -i; } | i=uint() { return i; }
     372}
     373
     374float ufloat() :
     375{
     376    Token f;
     377}
     378{
     379    ( f=<UFLOAT> | f=<UINT> )
     380    { return Float.parseFloat(f.image); }
     381}
     382
     383float float_() :
     384{
     385    float f;
     386}
     387{
     388    <MINUS> f=ufloat() { return -f; } | f=ufloat() { return f; }
     389}
     390
     391String string() :
     392{
     393    Token t;
     394}
     395{
     396    t=<STRING>
     397    { return t.image.substring(1, t.image.length() - 1).replace("\\\"", "\"").replace("\\\\", "\\"); }
     398}
     399
     400String ident():
     401{
     402    Token t;
     403    String s;
     404}
     405{
     406    ( t=<IDENT> | t=<SET> ) { return t.image; }
     407}
     408
     409String string_or_ident() :
     410{
     411    Token t;
     412    String s;
     413}
     414{
     415    ( s=ident() | s=string() ) { return s; }
     416}
     417
     418String regex() :
     419{
     420    Token t;
     421}
     422{
     423    t=<REGEX>
     424    { return t.image.substring(1, t.image.length() - 1); }
     425}
     426
     427/**
     428 * white-space
     429 */
     430void s() :
     431{
     432}
     433{
     434    ( <S> )?
     435}
     436
     437/**
     438 * mix of white-space and comments
     439 */
     440void w() :
     441{
     442}
     443{
     444    ( <S> | <COMMENT_START> <COMMENT_END> )*
     445}
     446
     447/**
     448 * comma delimited list of floats (at least 2, all &gt;= 0)
     449 */
     450List<Float> float_array() :
     451{
     452    float f;
     453    List<Float> fs = new ArrayList<Float>();
     454}
     455{
     456    f=ufloat() { fs.add(f); }
     457    (
     458        <COMMA> s()
     459        f=ufloat() { fs.add(f); }
     460    )+
     461    {
     462        return fs;
     463    }
     464}
     465
     466/**
     467 * entry point for the main parser
     468 */
     469void sheet(MapCSSStyleSource sheet):
    324470{
    325471    MapCSSRule r;
    326472}
    327473{
     474    { this.sheet = sheet; }
     475    w()
    328476    (
    329477        try {
    330             r=rule() { if (add && r != null) { sheet.rules.add(r); } } w()
     478            r=rule() { if (r != null) { sheet.rules.add(r); } } w()
    331479        } catch (MapCSSException mex) {
    332480            error_skipto(RBRACE, mex);
     
    337485        }
    338486    )*
     487    <EOF>
    339488}
    340489
     
    663812Expression function() :
    664813{
    665     Token tmp;
    666814    Expression arg;
    667815    String name;
     
    681829Object literal() :
    682830{
    683     String val;
    684     Token t, t2;
     831    String val, pref;
     832    Token t;
    685833    float f;
    686834}
    687835{
    688836        LOOKAHEAD(2)
    689         t2=<IDENT> t=<HEXCOLOR>
    690         { return Main.pref.getColor("mappaint." + (sheet == null ? "MapCSS" : sheet.title) + "." + t2.image, ColorHelper.html2color(t.image)); }
     837        pref=ident() t=<HEXCOLOR>
     838        { return Main.pref.getColor("mappaint." + (sheet == null ? "MapCSS" : sheet.title) + "." + pref, ColorHelper.html2color(t.image)); }
    691839    |
    692840        t=<IDENT> { return new Keyword(t.image); }
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java

    r6896 r6970  
    7878            InputStream in = getSourceInputStream();
    7979            try {
    80                 MapCSSParser parser = new MapCSSParser(in, "UTF-8");
     80                // evaluate @media { ... } blocks
     81                MapCSSParser preprocessor = new MapCSSParser(in, "UTF-8", MapCSSParser.LexicalState.PREPROCESSOR);
     82                String mapcss = preprocessor.pp_root(this);
     83               
     84                // do the actual mapcss parsing
     85                InputStream in2 = new ByteArrayInputStream(mapcss.getBytes(Utils.UTF_8));
     86                MapCSSParser parser = new MapCSSParser(in2, "UTF-8", MapCSSParser.LexicalState.DEFAULT);
    8187                parser.sheet(this);
     88               
    8289                loadMeta();
    8390                loadCanvas();
Note: See TracChangeset for help on using the changeset viewer.