diff --git a/autotests/folding/example.rmd.fold b/autotests/folding/example.rmd.fold index a0cf7f5..9fbe1e6 100644 --- a/autotests/folding/example.rmd.fold +++ b/autotests/folding/example.rmd.fold @@ -1,39 +1,39 @@ --- -title: "test" -author: "me" -date: "07.10.2014" +title: "test" +author: "me" +date: "07.10.2014" output: html_document --- This is a simple test document. It shows syntax highlighting switches between YAML (above), R blocks ```{r, echo=FALSE} -for (i in 1:10) { +for (i in 1:10) { if(i>=10) print(i) -} +} # two blank lines below sessionInfo() ``` LaTeX equations, $$ h_{i}(t \mid q,C) = h_{0}(t) e^{\beta_{1}quality_{i} + \beta_{2}C_{iq}} $$ and Markdown. A [link](http://example.com) in Markdown. Inline `r y <- 5 + x - sin(3)` R code. Inline `y <- 5 + x - sin(3)` code. Heading ======= Sub-heading ----------- A list of editors: * kate * vim * emacs *italic*, **bold**, `monospace` diff --git a/autotests/folding/highlight.php.fold b/autotests/folding/highlight.php.fold index afeccca..27891d7 100644 --- a/autotests/folding/highlight.php.fold +++ b/autotests/folding/highlight.php.fold @@ -1,69 +1,69 @@ /* This is a pseudo PHP file to test Kate's PHP syntax highlighting. */ # TODO: this is incomplete, add more syntax examples! # this is also a comment. // Even this is a comment function test($varname) { return "bla"; # this is also a comment } ?> test"; ?> > - .inputText { + .inputText { width: px; text-indent: 10px; - } + } > > var some_js_var = ; > type="text/babel"> - > > /* aaa */ ?> >> - function a(i) { + > > /* aaa */ ?> >> + function a(i) { - return >{ i + j }>; - } + return >{ i + j }>; + } > type="text/typescript"> - class DateTime { + class DateTime { info: string; - constructor() { this.info = ; } - get() { return this.info; } - } + constructor() { this.info = ; } + get() { return this.info; } + } > type="x-tmpl-mustache"> - {{! }} - {{#movie}} - > - >{{title}}> - src="{{poster}}" alt="{{title}}"/> + {{! }} + {{#movie}} + > +

{{title}}

+ {{title}} - {{ratings.critics_rating}} - > - {{/movie}} +
> + {{/movie}}
> << This is the $string inside the variable (which seems to be rendered as a string) It works well, I think. DOOH # bug 382527 throw new ParserException("Test {$this->some_var[$index]}\nin {$this->file} is missing.\nThis is bad."); ?> diff --git a/autotests/folding/test.htm.fold b/autotests/folding/test.htm.fold index 5600c7c..316dade 100644 --- a/autotests/folding/test.htm.fold +++ b/autotests/folding/test.htm.fold @@ -1,63 +1,69 @@ HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> This is a title > /* comment */ #xyz { color: red; } h1 { font: "Comic Sans"; } >

Hello, {{ name }}!

> document.body.appendChild(document.createTextNode('Hello World!')); // comment > type="text/typescript"> class Student { fullName: string; constructor(public firstName: string, public middleInitial: string, public lastName: string) { this.fullName = firstName + " " + middleInitial + " " + lastName; } } let a: null = null; let b: number = 12___3; > src="https://unpkg.com/babel-standalone@6/babel.min.js">> type="text/babel"> ReactDOM.render( >Hello, world!>, document.getElementById('root') ); function Story(props) { const SpecificStory = components[props.storyType]; return story={ props.story } attr2="&ref;" attr3="Hello\n" />; } > type="x-tmpl-mustache"> - {{#movie}} - > - >{{title}}> - src="{{poster}}" alt="{{title}}"/> - > - > + {{#movie}} + > +

{{title}}

+ {{title}} +
> + > Rating - {{ratings.critics_rating}} - > - {{/movie}} - {{^movie}} - > + > + {{/movie}} + {{^movie}} + > Movie Does Not Exist :( - > - {{/movie}} - {{! comment }} + > + {{/movie}} + {{! comment }}
> + + + type="text/html"> +
  • Hello
  • +
    > + diff --git a/autotests/folding/test.markdown.fold b/autotests/folding/test.markdown.fold index bba05c0..b7ab868 100644 --- a/autotests/folding/test.markdown.fold +++ b/autotests/folding/test.markdown.fold @@ -1,182 +1,182 @@ # H1 ## H2 ### H3 Multi-line paragraph bla bla bla bla bla bla. Intentional line break via two spaces at line. Formats: _italic_, **bold**, `monospace`, ~~strikeout~~ Bullet list: * item1 * item2 Numbered list: 1. item 1 2. item 2 [link](http://kde.org) code 1 code 2 normal text > block quote _italic_ > more block quote normal text Title: some text normal text # Fenced code blocks (bug 356974) ## Bash ```bash #!/usr/bin/env bash for f in *; do echo "$f" done ``` ## C++ ```cpp #include class Q : public QObject { Q_OBJECT public: Q(); } Q::Q() :QObject(NULL) { } ``` ## CMake ```cmake cmake_minimum_required(VERSION 3.10 FATAL_ERROR) project (hello_world) set(QT_MIN_VERSION "5.6.0") ``` ## CSS ```css h1.main::hover { font-size: 100; color: green; } ``` ## Email ```email From: Konqi Dragon To: All Subject: highlights ``` ## Haskell ```haskell module Main (main) where main :: IO () main = putStrLn "Hello, World!" ``` ## HTML ```html

    Hello world!

    ``` ## JSON ```json -[{ +[{ "hello": "world", "count": 1, "bool": true -}] +}] ``` ## KConfig ```kconfig [General] Toggle=true Count=1 ``` ## PHP ```php namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController -{ -} +{ +} ``` ## Python ```python -def addXToY(x, y): +def addXToY(x, y): total = x + y print total ``` ## QML ```qml -Text { +Text { id: hello width: 100 text: "Hello world!" -} +} ``` ## Rust ```rust -fn main() { +fn main() { println!("Hello world!"); -} +} ``` ## XML ```xml - attribute="3"> - /> -> + attribute="3"> + /> +> ``` ## No language specified ``` No language is specified, but it should be still rendered as code block. ``` diff --git a/autotests/folding/test.mustache.fold b/autotests/folding/test.mustache.fold new file mode 100644 index 0000000..93d7f3e --- /dev/null +++ b/autotests/folding/test.mustache.fold @@ -0,0 +1,110 @@ + +html> + + + + {{title}} + + + > + /* Mustache variable support in CSS is incomplete */ + {{mustache}} + [ {{other}} ] + body { + {{{other}}} + font-size: 15pt; + font-family: Verdana; + } + > + + > + var foo = {{bar}}; + `template {{foo}} ${ var a = {{b}} }` + > + + + {{#movie}} + > +

    {{title}}

    + {{title}} +
    > + > + Rating - {{ratings.critics_rating}} + > + {{/movie}} + {{^movie}} + > + Movie Does Not Exist :( + > + {{/movie}} + {{! this is a comment TODO ALERT }} + + This is a partial {{> partial1 }} + {{#block + param=foo + param2=bar}} + {{block foo=bar}} + {{/block}} + + + + {{#list people}}{{firstName}} {{lastName}}{{/list}} + {{#noop}}{{body}}{{/noop}} + {{#with story}} + class="intro">{{{intro}}}> + class="body">{{{body}}}> + {{/with}} + {{#each comments}} + class="comment"> +

    {{subject}}

    + {{{body}}} +
    > + {{/each}} + {{#list nav}} + {{title}} + {{/list}} + + {{!-- Conditionals --}} + {{#if isActive}} + Active + {{else if isInactive}} + Inactive + {{else}} + + {{/if}} + + {{!-- Escaping --}} + \{{escaped}} + {{{{raw}}}} + {{escaped}} + {{{{/raw}}}} + + {{!-- Whitespace Control --}} + {{#each nav ~}} + + {{~#if test}} + {{~title}} + {{~^~}} + Empty + {{~/if~}} + + {{~/each}} + + {{!-- Helpers --}} + {{link "See more..." story.url}} + {{log "Look at me!" level="error"}} + + {{!-- Partial --}} + {{> myPartial name=../name }} + {{#> myPartial }} + Failover content + {{/myPartial}} + {{#*inline "myPartial"}} + My Content + {{/inline}} + {{#each children}} + {{> myPartial}} + {{/each}} + + + diff --git a/autotests/html/highlight.php.html b/autotests/html/highlight.php.html index 034e4c6..5502f68 100644 --- a/autotests/html/highlight.php.html +++ b/autotests/html/highlight.php.html @@ -1,76 +1,76 @@ highlight.php
     <?
     /* This is a pseudo PHP file to test Kate's PHP syntax highlighting. */
     # TODO: this is incomplete, add more syntax examples!
     # this is also a comment.
     // Even this is a comment
     function test($varname) {
     	return "bla";	# this is also a comment
     }
     
     ?>
     
     <?php echo("hello test"); ?>
     
     <html>
     	<? print "<title>test</title>"; ?>
     
     	<!-- CSS -->
     	<style>
     		.inputText {
     			width: <?php echo $width; ?>px;
     			text-indent: 10px;
     		}
     	</style>
     
     	<!-- JavaScript -->
     	<script>
     		var some_js_var = <?php echo $somevar; ?> ;
     		<?php echo 'alert("Hello there.");'; ?>
     	</script>
     	<!-- JavaScript React -->
     	<script type="text/babel">
     		<Hello> <?php echo("Hello, hello!"); ?> </Hello> <?php /* aaa */ ?> <div></div>
     		function a(i) {
     			<?php echo "var j = 1;"; ?>
     			return <p>{ i + j }</p>;
     		}
     	</script>
     	<!-- TypeScript -->
     	<script type="text/typescript">
     		<?php $timestamp = time(); ?>
     		class DateTime {
     			info: string;
     			constructor() { this.info = <?php echo(date("F d, Y h:i:s", $timestamp)); ?>; }
     			get() { return this.info; }
     		}
     	</script>
     	<!-- MustacheJS -->
     	<script type="x-tmpl-mustache">
     		{{! <?php print "comment"; ?> }}
     		{{#movie}}
     			<div>
    -				<h1>{{title}}</h1>
    -				<img src="{{poster}}" alt="{{title}}"/>
    -				<?php echo $movierating; ?> - {{ratings.critics_rating}}
    +				<h1>{{title}}</h1>
    +				<img src="{{poster}}" alt="{{title}}"/>
    +				<?php echo $movierating; ?> - {{ratings.critics_rating}}
     			</div>
     		{{/movie}}
     	</script>
     
     </html>
     
     <?php
     $var = <<<DOOH
     This is the $string inside the variable (which seems to be rendered as a string)
     It works well, I think.
     DOOH
     
     # bug 382527
     throw new ParserException("Test {$this->some_var[$index]}\nin {$this->file} is missing.\nThis is bad.");
     ?>
     
    diff --git a/autotests/html/test.htm.html b/autotests/html/test.htm.html index 66d05d0..60b1ae3 100644 --- a/autotests/html/test.htm.html +++ b/autotests/html/test.htm.html @@ -1,70 +1,76 @@ test.htm
     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
     <html>
       <head>
         <title>This is a title</title>
         <style>
         /* comment */
         #xyz { color: red; }
         h1 { font: "Comic Sans"; }
         </style>
       </head>
       <body class="ui main">
         <p *ngFor="let name of names">Hello, {{ name }}!</p>
       </body>
     
       <!-- JavaScript code -->
       <script>
         document.body.appendChild(document.createTextNode('Hello World!')); // comment
       </script>
     
       <!-- TypeScript code -->
       <script type="text/typescript">
         class Student {
             fullName: string;
             constructor(public firstName: string, public middleInitial: string, public lastName: string) {
                 this.fullName = firstName + " " + middleInitial + " " + lastName;
             }
         }
         let a: null = null;
         let b: number = 12___3;
       </script>
     
       <!-- JSX code -->
       <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
       <script type="text/babel">
         ReactDOM.render(
           <h1>Hello, world!</h1>,
           document.getElementById('root')
         );
         function Story(props) {
           const SpecificStory = components[props.storyType];
           return <SpecificStory story={ props.story } attr2="&ref;" attr3="Hello\n" />;
         }
       </script>
     
       <!-- MustacheJS -->
       <script type="x-tmpl-mustache">
         {{#movie}}
             <div>
    -            <h1>{{title}}</h1>
    -            <img src="{{poster}}" alt="{{title}}"/>
    +            <h1>{{title}}</h1>
    +            <img src="{{poster}}" alt="{{title}}"/>
             </div>
             <div>
    -            Rating - {{ratings.critics_rating}}
    +            Rating - {{ratings.critics_rating}}
             </div>
         {{/movie}}
         {{^movie}}
             <div>
                 Movie Does Not Exist :(
             </div>
         {{/movie}}
    -    {{! comment }} 
    +    {{! comment }}
       </script>
    +
    +  <!-- HTML template -->
    +  <script type="text/html">
    +    <li><a href="link">Hello</a></li>
    +  </script>
    +
     </html>
     
    diff --git a/autotests/html/test.mustache.html b/autotests/html/test.mustache.html new file mode 100644 index 0000000..7dba1f1 --- /dev/null +++ b/autotests/html/test.mustache.html @@ -0,0 +1,117 @@ + + + +test.mustache + +
    +<!-- Mustache / Handlebars / Ractive template -->
    +<!DOCTYPE html>
    +<html>
    +    <head>
    +        <meta charset="UTF-8" />
    +        <title>{{title}}</title>
    +    </head>
    +    <!-- CSS -->
    +    <style>
    +        /* Mustache variable support in CSS is incomplete */
    +        {{mustache}}
    +        [ {{other}} ]
    +        body {
    +            {{{other}}}
    +            font-size: 15pt;
    +            font-family: Verdana;
    +        }
    +    </style>
    +    <!-- JavaScript -->
    +    <script>
    +        var foo = {{bar}};
    +        `template {{foo}} ${ var a = {{b}} }`
    +    </script>
    +
    +    <body>
    +    {{#movie}}
    +        <div>
    +            <h1>{{title}}</h1>
    +            <img src="{{poster}}" alt="{{title}}"/>
    +        </div>
    +        <div>
    +            Rating - {{ratings.critics_rating}}
    +        </div>
    +    {{/movie}}
    +    {{^movie}}
    +        <div>
    +            Movie Does Not Exist :(
    +        </div>
    +    {{/movie}}
    +    {{! this is a comment TODO ALERT }}
    +
    +    This is a partial {{> partial1 }}
    +    {{#block
    +        param=foo
    +        param2=bar}}
    +        {{block foo=bar}}
    +    {{/block}}
    +
    +    <!-- Handlebars -->
    +
    +    {{#list people}}{{firstName}} {{lastName}}{{/list}}
    +    {{#noop}}{{body}}{{/noop}}
    +    {{#with story}}
    +        <div class="intro">{{{intro}}}</div>
    +        <div class="body">{{{body}}}</div>
    +    {{/with}}
    +    {{#each comments}}
    +        <div class="comment">
    +            <h2>{{subject}}</h2>
    +            {{{body}}}
    +        </div>
    +    {{/each}}
    +    {{#list nav}}
    +        <a href="{{url}}">{{title}}</a>
    +    {{/list}}
    +
    +    {{!-- Conditionals --}}
    +    {{#if isActive}}
    +        <img src="star.gif" alt="Active">
    +    {{else if isInactive}}
    +        <img src="cry.gif" alt="Inactive">
    +    {{else}}
    +        <img src="cry.gif" alt="">
    +    {{/if}}
    +
    +    {{!-- Escaping --}}
    +    \{{escaped}}
    +    {{{{raw}}}}
    +        {{escaped}}
    +    {{{{/raw}}}}
    +
    +    {{!-- Whitespace Control --}}
    +    {{#each nav ~}}
    +        <a href="{{url}}">
    +            {{~#if test}}
    +                {{~title}}
    +            {{~^~}}
    +                Empty
    +            {{~/if~}}
    +        </a>
    +    {{~/each}}
    +
    +    {{!-- Helpers --}}
    +    {{link "See more..." story.url}}
    +    {{log "Look at me!" level="error"}}
    +
    +    {{!-- Partial --}}
    +    {{> myPartial name=../name }}
    +    {{#> myPartial }}
    +        Failover content
    +    {{/myPartial}}
    +    {{#*inline "myPartial"}}
    +        My Content
    +    {{/inline}}
    +    {{#each children}}
    +        {{> myPartial}}
    +    {{/each}}
    +
    +    </body>
    +</html>
    +
    diff --git a/autotests/input/test.htm b/autotests/input/test.htm index 600c351..5c137ce 100644 --- a/autotests/input/test.htm +++ b/autotests/input/test.htm @@ -1,63 +1,69 @@ This is a title

    Hello, {{ name }}!

    + + + + diff --git a/autotests/input/test.mustache b/autotests/input/test.mustache new file mode 100644 index 0000000..7f3f842 --- /dev/null +++ b/autotests/input/test.mustache @@ -0,0 +1,110 @@ + + + + + + {{title}} + + + + + + + + {{#movie}} +
    +

    {{title}}

    + {{title}} +
    +
    + Rating - {{ratings.critics_rating}} +
    + {{/movie}} + {{^movie}} +
    + Movie Does Not Exist :( +
    + {{/movie}} + {{! this is a comment TODO ALERT }} + + This is a partial {{> partial1 }} + {{#block + param=foo + param2=bar}} + {{block foo=bar}} + {{/block}} + + + + {{#list people}}{{firstName}} {{lastName}}{{/list}} + {{#noop}}{{body}}{{/noop}} + {{#with story}} +
    {{{intro}}}
    +
    {{{body}}}
    + {{/with}} + {{#each comments}} +
    +

    {{subject}}

    + {{{body}}} +
    + {{/each}} + {{#list nav}} + {{title}} + {{/list}} + + {{!-- Conditionals --}} + {{#if isActive}} + Active + {{else if isInactive}} + Inactive + {{else}} + + {{/if}} + + {{!-- Escaping --}} + \{{escaped}} + {{{{raw}}}} + {{escaped}} + {{{{/raw}}}} + + {{!-- Whitespace Control --}} + {{#each nav ~}} + + {{~#if test}} + {{~title}} + {{~^~}} + Empty + {{~/if~}} + + {{~/each}} + + {{!-- Helpers --}} + {{link "See more..." story.url}} + {{log "Look at me!" level="error"}} + + {{!-- Partial --}} + {{> myPartial name=../name }} + {{#> myPartial }} + Failover content + {{/myPartial}} + {{#*inline "myPartial"}} + My Content + {{/inline}} + {{#each children}} + {{> myPartial}} + {{/each}} + + + diff --git a/autotests/reference/highlight.php.ref b/autotests/reference/highlight.php.ref index 8a92751..a6452ee 100644 --- a/autotests/reference/highlight.php.ref +++ b/autotests/reference/highlight.php.ref @@ -1,69 +1,69 @@
    /* This is a pseudo PHP file to test Kate's PHP syntax highlighting. */
    # TODO: this is incomplete, add more syntax examples!
    # this is also a comment.
    // Even this is a comment
    function test($varname) {
    return "bla"; # this is also a comment
    }

    ?>

    echo("hello test"); ?>


    print "test"; ?>







    type="text/babel">
    echo("Hello, hello!"); ?> /* aaa */ ?>

    function a(i) {
    echo "var j = 1;"; ?>
    return

    { i + j }

    ;
    }


    type="text/typescript">
    $timestamp = time(); ?>
    class DateTime {
    info: string;
    constructor() { this.info = echo(date("F d, Y h:i:s", $timestamp)); ?>; }
    get() { return this.info; }
    }


    type="x-tmpl-mustache">
    - {{! print "comment"; ?> }}
    - {{#movie}}
    + {{! print "comment"; ?> }}
    + {{#movie}}

    -

    {{title}}


    - src="{{poster}}" alt="{{title}}"/>
    - echo $movierating; ?> - {{ratings.critics_rating}}
    +

    {{title}}


    + src="{{poster}}" alt="{{title}}"/>
    + echo $movierating; ?> - {{ratings.critics_rating}}

    - {{/movie}}
    + {{/movie}}





    $var = <<
    This is the $string inside the variable (which seems to be rendered as a string)
    It works well, I think.
    DOOH

    # bug 382527
    throw new ParserException("Test {$this->some_var[$index]}\nin {$this->file} is missing.\nThis is bad.");
    ?>
    diff --git a/autotests/reference/test.htm.ref b/autotests/reference/test.htm.ref index 9c87bf9..516ea28 100644 --- a/autotests/reference/test.htm.ref +++ b/autotests/reference/test.htm.ref @@ -1,63 +1,69 @@ HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">


    </Element><Normal Text>This is a title</Normal Text><Element>


    class="ui main">
    *ngFor="let name of names">Hello, {{ name }}!








    type="text/typescript">
    class Student {
    fullName: string;
    constructor(public firstName: string, public middleInitial: string, public lastName: string) {
    this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
    }
    let a: null = null;
    let b: number = 12___3;



    src="https://unpkg.com/babel-standalone@6/babel.min.js">
    type="text/babel">
    ReactDOM.render(

    Hello, world!

    ,
    document.getElementById('root')
    );
    function Story(props) {
    const SpecificStory = components[props.storyType];
    return story={ props.story } attr2="&ref;" attr3="Hello\n" />;
    }



    type="x-tmpl-mustache">
    - {{#movie}}
    + {{#movie}}

    -

    {{title}}


    - src="{{poster}}" alt="{{title}}"/>
    -

    +

    {{title}}


    + src="{{poster}}" alt="{{title}}"/>
    +

    - Rating - {{ratings.critics_rating}}
    + Rating - {{ratings.critics_rating}}

    - {{/movie}}
    - {{^movie}}
    + {{/movie}}
    + {{^movie}}

    - Movie Does Not Exist :(
    -

    - {{/movie}}
    - {{! comment }}
    + Movie Does Not Exist :(
    +
    + {{/movie}}
    + {{! comment }}

    +
    +
    + type="text/html">
    +
  • href="link">Hello

  • +
    +

    diff --git a/autotests/reference/test.mustache.ref b/autotests/reference/test.mustache.ref new file mode 100644 index 0000000..bf15709 --- /dev/null +++ b/autotests/reference/test.mustache.ref @@ -0,0 +1,110 @@ +
    +html>
    +
    +
    + charset="UTF-8" />
    + </Element Tag><Mustache Variable>{{</Mustache Variable><Mustache Inside>title</Mustache Inside><Mustache Variable>}}</Mustache Variable><Element Tag>
    +
    +
    +
    +
    +
    +
    +
    + {{#movie}}
    +

    +

    {{title}}


    + src="{{poster}}" alt="{{title}}"/>
    +

    +

    + Rating - {{ratings.critics_rating}}
    +

    + {{/movie}}
    + {{^movie}}
    +

    + Movie Does Not Exist :(
    +

    + {{/movie}}
    + {{! this is a comment TODO ALERT }}
    +
    + This is a partial {{> partial1 }}
    + {{#block
    + param=foo
    + param2=bar}}
    + {{block foo=bar}}
    + {{/block}}
    +
    +
    +
    + {{#list people}}{{firstName}} {{lastName}}{{/list}}
    + {{#noop}}{{body}}{{/noop}}
    + {{#with story}}
    + class="intro">{{{intro}}}
    + class="body">{{{body}}}
    + {{/with}}
    + {{#each comments}}
    + class="comment">
    +

    {{subject}}


    + {{{body}}}
    +
    + {{/each}}
    + {{#list nav}}
    + href="{{url}}">{{title}}
    + {{/list}}
    +
    + {{!-- Conditionals --}}
    + {{#if isActive}}
    + src="star.gif" alt="Active">
    + {{else if isInactive}}
    + src="cry.gif" alt="Inactive">
    + {{else}}
    + src="cry.gif" alt="">
    + {{/if}}
    +
    + {{!-- Escaping --}}
    + \{{escaped}}
    + {{{{raw}}}}
    + {{escaped}}
    + {{{{/raw}}}}
    +
    + {{!-- Whitespace Control --}}
    + {{#each nav ~}}
    + href="{{url}}">
    + {{~#if test}}
    + {{~title}}
    + {{~^~}}
    + Empty
    + {{~/if~}}
    +
    + {{~/each}}
    +
    + {{!-- Helpers --}}
    + {{link "See more..." story.url}}
    + {{log "Look at me!" level="error"}}
    +
    + {{!-- Partial --}}
    + {{> myPartial name=../name }}
    + {{#> myPartial }}
    + Failover content
    + {{/myPartial}}
    + {{#*inline "myPartial"}}
    + My Content
    + {{/inline}}
    + {{#each children}}
    + {{> myPartial}}
    + {{/each}}
    +
    +
    +
    diff --git a/autotests/syntaxrepository_test.cpp b/autotests/syntaxrepository_test.cpp index f3c7d2b..2c4b2a0 100644 --- a/autotests/syntaxrepository_test.cpp +++ b/autotests/syntaxrepository_test.cpp @@ -1,558 +1,558 @@ /* Copyright (C) 2016 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "test-config.h" #include #include #include #include #include #include #include #include #include #include namespace KSyntaxHighlighting { class NullHighlighter : public AbstractHighlighter { public: using AbstractHighlighter::highlightLine; void applyFormat(int offset, int length, const Format &format) Q_DECL_OVERRIDE { Q_UNUSED(offset); Q_UNUSED(length); // only here to ensure we don't crash format.isDefaultTextStyle(theme()); format.textColor(theme()); } }; class RepositoryTest : public QObject { Q_OBJECT private: Repository m_repo; private Q_SLOTS: void initTestCase() { QStandardPaths::enableTestMode(true); initRepositorySearchPaths(m_repo); } void testDefinitionByExtension_data() { QTest::addColumn("fileName"); QTest::addColumn("defName"); QTest::newRow("empty") << QString() << QString(); QTest::newRow("qml") << QStringLiteral("/bla/foo.qml") << QStringLiteral("QML"); QTest::newRow("glsl") << QStringLiteral("flat.frag") << QStringLiteral("GLSL"); // the following ones are defined in multiple syntax definitions QTest::newRow("c") << QStringLiteral("test.c") << QStringLiteral("C"); QTest::newRow("c++") << QStringLiteral("test.cpp") << QStringLiteral("C++"); QTest::newRow("markdown") << QStringLiteral("test.md") << QStringLiteral("Markdown"); QTest::newRow("Makefile 1") << QStringLiteral("Makefile") << QStringLiteral("Makefile"); QTest::newRow("Makefile 2") << QStringLiteral("/some/path/to/Makefile") << QStringLiteral("Makefile"); QTest::newRow("Makefile 3") << QStringLiteral("Makefile.am") << QStringLiteral("Makefile"); } void testDefinitionByExtension() { QFETCH(QString, fileName); QFETCH(QString, defName); auto def = m_repo.definitionForFileName(fileName); if (defName.isEmpty()) { QVERIFY(!def.isValid()); } else { QVERIFY(def.isValid()); QCOMPARE(def.name(), defName); } } void testDefinitionsForFileName_data() { QTest::addColumn("fileName"); QTest::addColumn("expectedNames"); QTest::newRow("Matlab") << QStringLiteral("/bla/foo.m") << (QStringList() << QStringLiteral("Magma") << QStringLiteral("Matlab") << QStringLiteral("Octave") << QStringLiteral("Objective-C")); } void testDefinitionsForFileName() { QFETCH(QString, fileName); QFETCH(QStringList, expectedNames); const auto defs = m_repo.definitionsForFileName(fileName); QStringList names; for (auto def : defs) { names.push_back(def.name()); } QCOMPARE(names, expectedNames); } void testDefinitionsForMimeType_data() { QTest::addColumn("mimeType"); QTest::addColumn("expectedNames"); QTest::newRow("C Header") << QStringLiteral("text/x-chdr") << (QStringList() << QStringLiteral("C++") << QStringLiteral("ISO C++") << QStringLiteral("C") << QStringLiteral("GCCExtensions") << QStringLiteral("ANSI C89") << QStringLiteral("SystemC")); } void testDefinitionsForMimeType() { QFETCH(QString, mimeType); QFETCH(QStringList, expectedNames); const auto defs = m_repo.definitionsForMimeType(mimeType); QStringList names; for (auto def : defs) { names.push_back(def.name()); } QCOMPARE(names, expectedNames); } void testLoadAll() { for (const auto &def : m_repo.definitions()) { QVERIFY(!def.name().isEmpty()); QVERIFY(!def.translatedName().isEmpty()); QVERIFY(!def.isValid() || !def.section().isEmpty()); QVERIFY(!def.isValid() || !def.translatedSection().isEmpty()); // indirectly trigger loading, as we can't reach that from public API // if the loading fails the highlighter will produce empty states NullHighlighter hl; State initialState; hl.setDefinition(def); const auto state = hl.highlightLine(QLatin1String("This should not crash } ] ) !"), initialState); QVERIFY(!def.isValid() || state != initialState); } } void testMetaData() { auto def = m_repo.definitionForName(QLatin1String("Alerts")); QVERIFY(def.isValid()); QVERIFY(def.extensions().isEmpty()); QVERIFY(def.mimeTypes().isEmpty()); QVERIFY(def.version() >= 1.11f); QVERIFY(def.isHidden()); QCOMPARE(def.section(), QLatin1String("Other")); QCOMPARE(def.license(), QLatin1String("MIT")); QVERIFY(def.author().contains(QLatin1String("Dominik"))); QFileInfo fi(def.filePath()); QVERIFY(fi.isAbsolute()); QVERIFY(def.filePath().endsWith(QLatin1String("alert.xml"))); def = m_repo.definitionForName(QLatin1String("C++")); QVERIFY(def.isValid()); QCOMPARE(def.section(), QLatin1String("Sources")); QCOMPARE(def.indenter(), QLatin1String("cstyle")); QCOMPARE(def.style(), QLatin1String("C++")); QVERIFY(def.mimeTypes().contains(QLatin1String("text/x-c++hdr"))); QVERIFY(def.extensions().contains(QLatin1String("*.h"))); QCOMPARE(def.priority(), 9); def = m_repo.definitionForName(QLatin1String("Apache Configuration")); QVERIFY(def.isValid()); QVERIFY(def.extensions().contains(QLatin1String("httpd.conf"))); QVERIFY(def.extensions().contains(QLatin1String(".htaccess*"))); } void testGeneralMetaData() { auto def = m_repo.definitionForName(QLatin1String("C++")); QVERIFY(def.isValid()); QVERIFY(!def.indentationBasedFoldingEnabled()); // comment markers QCOMPARE(def.singleLineCommentMarker(), QLatin1String("//")); QCOMPARE(def.singleLineCommentPosition(), KSyntaxHighlighting::CommentPosition::StartOfLine); const auto cppMultiLineCommentMarker = QPair(QLatin1String("/*"), QLatin1String("*/")); QCOMPARE(def.multiLineCommentMarker(), cppMultiLineCommentMarker); def = m_repo.definitionForName(QLatin1String("Python")); QVERIFY(def.isValid()); // indentation QVERIFY(def.indentationBasedFoldingEnabled()); QCOMPARE(def.foldingIgnoreList(), QStringList() << QLatin1String("(?:\\s+|\\s*#.*)")); // keyword lists QVERIFY(!def.keywordLists().isEmpty()); QVERIFY(def.keywordList(QLatin1String("operators")).contains(QLatin1String("and"))); QVERIFY(!def.keywordList(QLatin1String("does not exits")).contains(QLatin1String("and"))); } void testFormatData() { auto def = m_repo.definitionForName(QLatin1String("ChangeLog")); QVERIFY(def.isValid()); auto formats = def.formats(); QVERIFY(!formats.isEmpty()); // verify that the formats are sorted, such that the order matches the order of the itemDatas in the xml files. auto sortComparator = [](const KSyntaxHighlighting::Format & lhs, const KSyntaxHighlighting::Format & rhs) { return lhs.id() < rhs.id(); }; QVERIFY(std::is_sorted(formats.begin(), formats.end(), sortComparator)); // check all names are listed QStringList formatNames; for (const auto & format : qAsConst(formats)) { formatNames.append(format.name()); } const QStringList expectedItemDatas = { QStringLiteral("Normal Text"), QStringLiteral("Name"), QStringLiteral("E-Mail"), QStringLiteral("Date"), QStringLiteral("Entry") }; QCOMPARE(formatNames, expectedItemDatas); } void testIncludedDefinitions() { auto def = m_repo.definitionForName(QLatin1String("PHP (HTML)")); QVERIFY(def.isValid()); auto defs = def.includedDefinitions(); const QStringList expectedDefinitionNames = { QStringLiteral("PHP/PHP"), QStringLiteral("Alerts"), QStringLiteral("CSS/PHP"), QStringLiteral("JavaScript/PHP"), QStringLiteral("Doxygen"), QStringLiteral("JavaScript React/PHP"), QStringLiteral("TypeScript/PHP"), - QStringLiteral("MustacheJS/PHP"), + QStringLiteral("Mustache/Handlebars (HTML)/PHP"), QStringLiteral("Modelines"), QStringLiteral("HTML"), QStringLiteral("CSS"), QStringLiteral("SQL (MySQL)"), QStringLiteral("JavaScript"), QStringLiteral("JavaScript React"), QStringLiteral("TypeScript"), - QStringLiteral("MustacheJS") + QStringLiteral("Mustache/Handlebars (HTML)") }; QStringList definitionNames; for (auto d : defs) { QVERIFY(d.isValid()); definitionNames.push_back(d.name()); // already check here a bit to make the test fails better fixable if (definitionNames.size() <= expectedDefinitionNames.size()) { QCOMPARE(d.name(), expectedDefinitionNames[definitionNames.size()-1]); } else { QCOMPARE(d.name(), QStringLiteral("too many included defs found, first one is this one")); } } QCOMPARE(definitionNames, expectedDefinitionNames); } void testIncludedFormats() { QStringList definitionNames; for (const auto &def : m_repo.definitions()) { definitionNames.push_back(def.name()); } for (const QString & name : qAsConst(definitionNames)) { Repository repo; initRepositorySearchPaths(repo); auto def = repo.definitionForName(name); QCOMPARE(m_repo.definitionForName(name).isValid(), def.isValid()); auto includedDefs = def.includedDefinitions(); includedDefs.push_front(def); // collect all formats, shall be numbered from 1.. QSet formatIds; for (auto d : qAsConst(includedDefs)) { const auto formats = d.formats(); for (const auto format : formats) { // no duplicates QVERIFY(!formatIds.contains(format.id())); formatIds.insert(format.id()); } } QVERIFY(!def.isValid() || !formatIds.isEmpty()); // ensure all ids are there from 1..size for (int i = 1; i <= formatIds.size(); ++i) { QVERIFY(formatIds.contains(i)); } } } void testReload() { auto def = m_repo.definitionForName(QLatin1String("QML")); QVERIFY(!m_repo.definitions().isEmpty()); QVERIFY(def.isValid()); NullHighlighter hl; hl.setDefinition(def); auto oldState = hl.highlightLine(QLatin1String("/* TODO this should not crash */"), State()); m_repo.reload(); QVERIFY(!m_repo.definitions().isEmpty()); QVERIFY(!def.isValid()); hl.highlightLine(QLatin1String("/* TODO this should not crash */"), State()); hl.highlightLine(QLatin1String("/* FIXME neither should this crash */"), oldState); QVERIFY(hl.definition().isValid()); QCOMPARE(hl.definition().name(), QLatin1String("QML")); } void testLifetime() { // common mistake with value-type like Repo API, make sure this doesn'T crash NullHighlighter hl; { Repository repo; hl.setDefinition(repo.definitionForName(QLatin1String("C++"))); hl.setTheme(repo.defaultTheme()); } hl.highlightLine(QLatin1String("/**! @todo this should not crash .*/"), State()); } void testCustomPath() { QString testInputPath = QStringLiteral(TESTSRCDIR "/input"); Repository repo; QVERIFY(repo.customSearchPaths().empty()); repo.addCustomSearchPath(testInputPath); QCOMPARE(repo.customSearchPaths().size(), 1); QCOMPARE(repo.customSearchPaths()[0], testInputPath); auto customDefinition = repo.definitionForName(QLatin1String("Test Syntax")); QVERIFY(customDefinition.isValid()); auto customTheme = repo.theme(QLatin1String("Test Theme")); QVERIFY(customTheme.isValid()); } void testInvalidDefinition() { Definition def; QVERIFY(!def.isValid()); QVERIFY(def.filePath().isEmpty()); QCOMPARE(def.name(), QLatin1String("None")); QVERIFY(def.section().isEmpty()); QVERIFY(def.translatedSection().isEmpty()); QVERIFY(def.mimeTypes().isEmpty()); QVERIFY(def.extensions().isEmpty()); QCOMPARE(def.version(), 0); QCOMPARE(def.priority(), 0); QVERIFY(!def.isHidden()); QVERIFY(def.style().isEmpty()); QVERIFY(def.indenter().isEmpty()); QVERIFY(def.author().isEmpty()); QVERIFY(def.license().isEmpty()); QVERIFY(!def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); QVERIFY(def.foldingIgnoreList().isEmpty()); QVERIFY(def.keywordLists().isEmpty()); QVERIFY(def.formats().isEmpty()); QVERIFY(def.includedDefinitions().isEmpty()); QVERIFY(def.singleLineCommentMarker().isEmpty()); QCOMPARE(def.singleLineCommentPosition(), KSyntaxHighlighting::CommentPosition::StartOfLine); const auto emptyPair = QPair(); QCOMPARE(def.multiLineCommentMarker(), emptyPair); QVERIFY(def.characterEncodings().isEmpty()); for (QChar c : QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~")) { QVERIFY(def.isWordDelimiter(c)); QVERIFY(def.isWordWrapDelimiter(c)); } } void testDelimiters() { auto def = m_repo.definitionForName(QLatin1String("LaTeX")); QVERIFY(def.isValid()); // check that backslash '\' is removed for (QChar c : QStringLiteral("\t !%&()*+,-./:;<=>?[]^{|}~")) QVERIFY(def.isWordDelimiter(c)); QVERIFY(!def.isWordDelimiter(QLatin1Char('\\'))); // check where breaking a line is valid for (QChar c : QStringLiteral(",{}[]")) QVERIFY(def.isWordWrapDelimiter(c)); } void testFoldingEnabled() { // test invalid folding Definition def; QVERIFY(!def.isValid()); QVERIFY(!def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); // test no folding def = m_repo.definitionForName(QLatin1String("ChangeLog")); QVERIFY(def.isValid()); QVERIFY(!def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); // C++ itself has no regions, but it includes ISO C++ def = m_repo.definitionForName(QLatin1String("C++")); QVERIFY(def.isValid()); QVERIFY(def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); // ISO C++ itself has folding regions def = m_repo.definitionForName(QLatin1String("ISO C++")); QVERIFY(def.isValid()); QVERIFY(def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); // Python has indentation based folding def = m_repo.definitionForName(QLatin1String("Python")); QVERIFY(def.isValid()); QVERIFY(def.foldingEnabled()); QVERIFY(def.indentationBasedFoldingEnabled()); } void testCharacterEncodings() { auto def = m_repo.definitionForName(QLatin1String("LaTeX")); QVERIFY(def.isValid()); const auto encodings = def.characterEncodings(); QVERIFY(!encodings.isEmpty()); QVERIFY(encodings.contains({ QChar(196), QLatin1String("\\\"{A}") })); QVERIFY(encodings.contains({ QChar(227), QLatin1String("\\~{a}") })); } void testIncludeKeywordLists() { Repository repo; QTemporaryDir dir; // forge a syntax file { QVERIFY(QDir(dir.path()).mkpath(QLatin1String("syntax"))); const char syntax[] = R"xml( c a b a c b d e##AAA e f f )xml"; QFile file(dir.path() + QLatin1String("/syntax/a.xml")); QVERIFY(file.open(QIODevice::WriteOnly)); QTextStream stream(&file); stream << syntax; } repo.addCustomSearchPath(dir.path()); auto def = repo.definitionForName(QLatin1String("AAA")); QCOMPARE(def.name(), QLatin1String("AAA")); auto klist1 = def.keywordList(QLatin1String("a")); auto klist2 = def.keywordList(QLatin1String("b")); auto klist3 = def.keywordList(QLatin1String("c")); // internal QHash is arbitrarily ordered and undeterministic auto& klist = klist1.size() == 3 ? klist1 : klist2.size() == 3 ? klist2 : klist3; QCOMPARE(klist.size(), 3); QVERIFY(klist.contains(QLatin1String("a"))); QVERIFY(klist.contains(QLatin1String("b"))); QVERIFY(klist.contains(QLatin1String("c"))); klist = def.keywordList(QLatin1String("d")); QCOMPARE(klist.size(), 3); QVERIFY(klist.contains(QLatin1String("d"))); QVERIFY(klist.contains(QLatin1String("e"))); QVERIFY(klist.contains(QLatin1String("f"))); klist = def.keywordList(QLatin1String("e")); QCOMPARE(klist.size(), 2); QVERIFY(klist.contains(QLatin1String("e"))); QVERIFY(klist.contains(QLatin1String("f"))); klist = def.keywordList(QLatin1String("f")); QCOMPARE(klist.size(), 1); QVERIFY(klist.contains(QLatin1String("f"))); } }; } QTEST_GUILESS_MAIN(KSyntaxHighlighting::RepositoryTest) #include "syntaxrepository_test.moc" diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 2550ef8..72edf37 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,68 +1,68 @@ # generate PHP definitions macro(generate_php_syntax_definition targetFile srcFile) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/generated/syntax) execute_process(COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generators/generate-php.pl INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/syntax/${srcFile} OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/generated/syntax/${targetFile}) endmacro() generate_php_syntax_definition(javascript-php.xml javascript.xml) generate_php_syntax_definition(css-php.xml css.xml) generate_php_syntax_definition(html-php.xml html.xml) generate_php_syntax_definition(javascript-react-php.xml javascript-react.xml) generate_php_syntax_definition(typescript-php.xml typescript.xml) -generate_php_syntax_definition(mustache-js-php.xml mustache-js.xml) +generate_php_syntax_definition(mustache-php.xml mustache.xml) # find all definitions file(GLOB src_defs "${CMAKE_CURRENT_SOURCE_DIR}/syntax/*.xml") set(defs ${src_defs} ${CMAKE_CURRENT_BINARY_DIR}/generated/syntax/html-php.xml ${CMAKE_CURRENT_BINARY_DIR}/generated/syntax/css-php.xml ${CMAKE_CURRENT_BINARY_DIR}/generated/syntax/javascript-php.xml ${CMAKE_CURRENT_BINARY_DIR}/generated/syntax/javascript-react-php.xml ${CMAKE_CURRENT_BINARY_DIR}/generated/syntax/typescript-php.xml - ${CMAKE_CURRENT_BINARY_DIR}/generated/syntax/mustache-js-php.xml + ${CMAKE_CURRENT_BINARY_DIR}/generated/syntax/mustache-php.xml ) # theme data resource qt5_add_resources(themes_QRC ${CMAKE_CURRENT_SOURCE_DIR}/themes/theme-data.qrc) # do we want syntax files bundled in the library? if (QRC_SYNTAX) # generate the resource file set(qrc_file ${CMAKE_CURRENT_BINARY_DIR}/syntax-data.qrc) set(qrc_body "") foreach(def ${defs}) get_filename_component(def_name ${def} NAME) string(APPEND qrc_body "${def}\n") endforeach() set(SYNTAX_DATA_QRC_FILES_STRING ${qrc_body}) configure_file(syntax-data.qrc.in ${qrc_file} @ONLY) # generate the index file add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/index.katesyntax" COMMAND katehighlightingindexer "${CMAKE_CURRENT_BINARY_DIR}/index.katesyntax" "${CMAKE_CURRENT_SOURCE_DIR}/schema/language.xsd" "${CMAKE_CURRENT_BINARY_DIR}/syntax-data.qrc" DEPENDS ${defs} ${CMAKE_CURRENT_SOURCE_DIR}/schema/language.xsd ${CMAKE_CURRENT_BINARY_DIR}/syntax-data.qrc ) # generate the qrc file manually, to make dependencies on generated files work... add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/qrc_syntax-data.cpp" COMMAND ${Qt5Core_RCC_EXECUTABLE} --name syntax_data -o "${CMAKE_CURRENT_BINARY_DIR}/qrc_syntax-data.cpp" "${CMAKE_CURRENT_BINARY_DIR}/syntax-data.qrc" DEPENDS ${defs} ${CMAKE_CURRENT_BINARY_DIR}/index.katesyntax ) set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/qrc_syntax-data.cpp" PROPERTIES SKIP_AUTOMOC ON) # object library to make cross-folder dependencies work, themes + syntax files add_library(SyntaxHighlightingData OBJECT ${themes_QRC} ${CMAKE_CURRENT_BINARY_DIR}/qrc_syntax-data.cpp) else() # install the syntax files as normal files into the prefix install (FILES ${defs} DESTINATION share/org.kde.syntax-highlighting/syntax) # object library to make cross-folder dependencies work, only themes add_library(SyntaxHighlightingData OBJECT ${themes_QRC}) endif() # set PIC to allow use in static and shared libs set_property(TARGET SyntaxHighlightingData PROPERTY POSITION_INDEPENDENT_CODE 1) target_link_libraries(SyntaxHighlightingData PRIVATE Qt5::Core) diff --git a/data/syntax/html.xml b/data/syntax/html.xml index 915ea6e..107e106 100644 --- a/data/syntax/html.xml +++ b/data/syntax/html.xml @@ -1,289 +1,307 @@ ]> - + + + + + + - - + + + + + + + + + + + + + + + diff --git a/data/syntax/mustache-js.xml b/data/syntax/mustache-js.xml deleted file mode 100644 index 2f4bc0f..0000000 --- a/data/syntax/mustache-js.xml +++ /dev/null @@ -1,326 +0,0 @@ - - - - - - - - -]> - - - - - diff --git a/data/syntax/mustache.xml b/data/syntax/mustache.xml new file mode 100644 index 0000000..c83b350 --- /dev/null +++ b/data/syntax/mustache.xml @@ -0,0 +1,470 @@ + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +