├── .gitignore ├── CHANGELOG ├── LICENSE ├── README.md ├── ROADMAP ├── composer.json ├── examples ├── .gitignore ├── README.md ├── availability │ ├── .gitignore │ ├── Main.elm │ ├── Makefile │ ├── README.md │ ├── api.php │ ├── composer.json │ ├── elm.json │ ├── elm.min.js │ ├── ical.php │ ├── index.html │ ├── index.html,ship │ ├── random.php │ └── regular.php ├── basic │ ├── .gitignore │ ├── BasicGenerator1.php │ ├── BasicGenerator2.php │ ├── BasicGenerator3.php │ ├── README.md │ └── composer.json ├── bocasucia │ ├── .gitignore │ ├── BocaSuciaGenerator.php │ ├── bocasucia.php │ ├── composer.json │ ├── lexicon.json │ └── ontology.json ├── chatbot │ ├── .gitignore │ ├── README.md │ ├── TruckConfigGenerator.php │ ├── botman_example.php │ ├── chat.html │ ├── composer.json │ ├── composer.lock │ ├── index.html │ └── local │ │ ├── chat.js │ │ ├── chat.min.css │ │ └── widget.js ├── german │ ├── .gitignore │ ├── ConfigurationConversation.php │ ├── MultilingualTruckConfigGenerator.php │ ├── README.md │ ├── botman_example.php │ ├── chat.html │ ├── composer.json │ ├── generated.txt │ ├── index.html │ ├── lexicon_de.json │ ├── lexicon_en.json │ ├── ontology.json │ └── run_all.php ├── multilingual │ ├── .gitignore │ ├── BudgetCommentaryGenerator.php │ ├── Predicate.php │ ├── README.md │ ├── budget_commentary.php │ ├── budgetplateau_commentary.user.js │ ├── composer.json │ ├── driver.php │ ├── example.rules │ ├── lexicon_en.json │ ├── lexicon_fr.json │ ├── ontology.json │ ├── rules.php │ ├── rules2php.php │ ├── sample.json │ ├── sample2.json │ └── sample3.json ├── ste │ ├── .gitignore │ ├── README.md │ ├── composer.json │ ├── example.php │ └── sentence.deps ├── tarot │ ├── .gitignore │ ├── TarotGenerator.php │ ├── composer.json │ ├── lexicon.json │ ├── ontology.json │ └── tarot.php └── webnlg │ ├── .gitignore │ ├── README.md │ └── webnlg_driver.php ├── phpunit.xml ├── scripts └── compileGrammars.php ├── src ├── Generator.php ├── Grammars │ ├── Availability │ │ ├── AvailabilityGenerator.php │ │ ├── AvailabilityGrammar.php │ │ ├── FocusedSegmentMessage.php │ │ ├── MeetingBlockMessage.php │ │ ├── lexicon_en.json │ │ └── util.php │ └── SimpleTechnicalEnglish.php ├── Lexicon.php └── Ontology.php └── tests ├── Availability └── AvailabilityTest.php ├── GeneratorTest.php ├── LexiconTest.php ├── OntologyTest.php └── SimpleGenerator.php /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | composer.lock 3 | vendor 4 | .phpunit.result.cache 5 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | php-nlgen (0.18) 2 | 3 | * Availability fixes plus web demo. 4 | 5 | -- Pablo Duboue, 2022/10/11 6 | 7 | php-nlgen (0.17) 8 | 9 | * A full grammar for generating availability descriptions based on schedules. 10 | * Lexicon: added query_string to streamline calls. 11 | * Generator: compilation with namespaces and wired-in lexicons and ontology. 12 | * Basic test cases. 13 | 14 | -- Pablo Duboue, 2022/10/05 15 | 16 | php-nlgen (0.16) 17 | 18 | * Lexicon query improvements and error notifications. 19 | 20 | -- Pablo Duboue, 2022/08/30 21 | 22 | php-nlgen (0.15) 23 | 24 | * Lexicon now takes slotted strings. 25 | 26 | -- Pablo Duboue, 2022/08/24 27 | 28 | php-nlgen (0.14) 29 | 30 | * Method interception now does not pollute the semantic stack with 31 | '_orig' or the language used. 32 | * Added new static 'Compile' method to use in servers with no 'eval' enabled. 33 | * Improved basic examples. 34 | 35 | -- Pablo Duboue, 2022/08/24 36 | 37 | php-nlgen (0.13) 38 | 39 | * Method interception now works with multilingual extensions. 40 | 41 | -- Pablo Duboue, 2022/08/22 42 | 43 | php-nlgen (0.12) 44 | 45 | * Published the SimpleTechnicalEnglish as a grammar part of the 46 | library instead of an example. 47 | 48 | -- Pablo Duboue, 2020/09/06 49 | 50 | php-nlgen (0.11) 51 | 52 | * Uppercase names for packagist release 53 | * composer.json 54 | 55 | -- Pablo Duboue, 2020/09/06 56 | 57 | php-nlgen (0.10) 58 | 59 | * Renamed php-nlgen to nlgen to match package name 60 | 61 | -- Pablo Duboue, 2019/09/01 62 | 63 | php-nlgen (0.9) 64 | 65 | * Botman example 66 | 67 | -- Pablo Duboue, 2019/03/27 68 | 69 | php-nlgen (0.8) 70 | 71 | * WebNLG example 72 | 73 | -- Pablo Duboue, 2017/08/18 74 | 75 | php-nlgen (0.7) 76 | 77 | * Sentence compression example 78 | 79 | -- Pablo Duboue, 2016/11/30 80 | 81 | php-nlgen (0.6) 82 | 83 | * Improved error logging 84 | 85 | -- Pablo Duboue, 2016/01/03 86 | 87 | php-nlgen (0.5) 88 | 89 | * Method interception 90 | 91 | -- Pablo Duboue, 2013/06/14 92 | 93 | php-nlgen (0.4) 94 | 95 | * Fixed for newer PHP version 96 | 97 | -- Pablo Duboue, 2013/06/13 98 | 99 | php-nlgen (0.3) 100 | 101 | * Multilingual extensions 102 | 103 | -- Pablo Duboue, 2011/11/29 104 | 105 | php-nlgen (0.2) 106 | 107 | * Lexicon improvements ('function' and 'mixed' entries) 108 | 109 | -- Pablo Duboue, 2011/09/06 110 | 111 | php-nlgen (0.1) 112 | 113 | * Initial version 114 | 115 | -- Pablo Duboue, 2011/08/15 116 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2020 Pablo Ariel Duboue 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](http://poser.pugx.org/nlgen/nlgen/v)](https://packagist.org/packages/nlgen/nlgen) [![Total Downloads](http://poser.pugx.org/nlgen/nlgen/downloads)](https://packagist.org/packages/nlgen/nlgen) [![Latest Unstable Version](http://poser.pugx.org/nlgen/nlgen/v/unstable)](https://packagist.org/packages/nlgen/nlgen) [![License](http://poser.pugx.org/nlgen/nlgen/license)](https://packagist.org/packages/nlgen/nlgen) 2 | 3 | # NLGen: a library for creating recursive-descent natural language generators 4 | 5 | These are pure PHP helper classes to implement recursive-descent 6 | natural language generators [1]. The classes provided are an abstract 7 | generator, an ontology container and a lexicon container. 8 | 9 | These classes should help build simple to mid-level generators, 10 | speaking about their complexity. Emphasis has been made in keeping 11 | more advanced features out of the way for simpler cases (i.e., if 12 | there is no need to use the ontology or the lexicon, they can be 13 | skipped). 14 | 15 | The generator keeps track of semantic annotations on the generated 16 | text, so as to enable further generation functions to reason about the 17 | text. A global context blackboard is also available. 18 | 19 | For details on the multilingual example see the Make Web Not War talk. [2] 20 | 21 | This is work in progress, see the ROADMAP for some insights in future 22 | development. 23 | 24 | * [1] http://duboue.net/blog5.html 25 | * [2] http://duboue.net/papers/makewebnotwar20111128.html 26 | 27 | 28 | ## Available Generation Grammars 29 | 30 | NLGen ships with a generation grammar ready to use, that constructs 31 | text descriptions for weekly schedules. The grammar is accessible by 32 | importing `\NLGen\Grammars\Availability\AvailabilityGenerator`. 33 | 34 | The method `generateAvailability` receives a list of "busy times" in 35 | the form of 36 | 37 | `[ day-of-week, [ start hour, start minute ], [ end hour, end minute ] ]` 38 | 39 | a list of ranges indicating when the scheduled day starts and ends (in 40 | the form of `[ day-of-week => [ start hour, start minute ], [ end 41 | hour, end minute ] ]`) and a constant indicating how "coarse" should 42 | be the text (one liner summarizing or very detailed). 43 | 44 | See `examples/availability` and `tests/Availability/AvailabilityTest`. 45 | 46 | Example: 47 | 48 | ```php 49 | use NLGen\Grammars\Availability\AvailabilityGenerator; 50 | 51 | $gen = new AvailabilityGenerator(); 52 | $busyList = [ 53 | [3, [16, 30], [17, 30] ], 54 | [6, [ 6, 55], [11, 41] ], 55 | [6, [14, 32], [22, 05] ] 56 | ]; 57 | $fullRanges = []; 58 | foreach(range(0, 6) as $dow) { 59 | $fullRanges[$dow] = [ [6, 0], [24, 0] ]; 60 | } 61 | echo $gen->generateAvailability($busyList, $fullRanges, AvailabilityGenerator::BASE, null); 62 | ``` 63 | 64 | Produces _All week is mostly free all day. Sunday is busy from late 6 AM to late 11 AM, and from half past 14 PM to 22 PM; the rest is free._ 65 | 66 | 67 | ## Using it in your own projects 68 | 69 | Look at the `examples/` folder, but in a nutshell, subclass the 70 | `NLGen\Generator` class and implemented a function named `top`. This 71 | function can return either a string or an array with a `text` and 72 | `sem` for semantic annotations on the returned text. 73 | 74 | If you want to use other functions to assemble the text use 75 | `$this->gen('name_of_the_function', 76 | $data_array_input_to_the_function)` to call it (instead of 77 | `$this->name_of_the_function($data_array_input_to_the_function)`. Or 78 | you can define your functions as *protected* and use function 79 | interposition, described below. The generator abstract class keeps 80 | track of the semantic annotations for you and other goodies. 81 | 82 | If the functions that implement the grammar are *protected*, a dynamic 83 | class can be created with the `NewSealed` class method. This dynamic 84 | class will have function interception so you can call 85 | `$this->name_of_function` as usual but actually `$this->gen` will be 86 | called. 87 | 88 | Either way you use it, to call the class, if your instantiated 89 | subclass is `$my_gen` then `$my_gen->generate($input_data_as_an_array)` 90 | will return the generated strings. If you want to access the semantic 91 | annotations, use `$my_gen->semantics()` afterward. 92 | 93 | For different use cases, see the `examples/` folder. 94 | 95 | 96 | ## Most basic example 97 | 98 | This example is grafted from the `examples/basic` folder. To be 99 | invoked command-line with `php basic.php 0 0 0 0` (it produces _Juan 100 | started working on Component ABC_). 101 | 102 | ```php 103 | class BasicGenerator extends Generator { 104 | 105 | var $agents = array('Juan','Pedro','The helpdesk operator'); 106 | var $events = array('started','is','finished'); 107 | var $actions = array('working on','coding','doing QA on'); 108 | var $themes = array('Component ABC','Item 25','the delivery subsystem'); 109 | 110 | protected function top($data){ 111 | return 112 | $this->person($data[0]). " " . 113 | $this->action($data[1], $data[2]). " " . 114 | $this->item($data[3]); 115 | } 116 | 117 | protected function person($agt){ return $this->agents[$agt]; } 118 | protected function action($evt, $act){ return $this->events[$evt]." ".$this->actions[$act]; } 119 | protected function item($thm) { return $this->themes[$thm]; } 120 | } 121 | 122 | global $argv,$argc; 123 | $gen = BasicGenerator::NewSealed(); 124 | print $gen->generate(array_splice($argv,1) /*,array("debug"=>1)*/)."\n"; 125 | ``` 126 | 127 | 128 | ## Learning more about NLG 129 | 130 | I highly recommend Building Natural Language Generation Systems (2000) 131 | by Reiter and Dale. 132 | 133 | The SIGGEN site [2] has plenty of good resources. You might also want 134 | to look at the NLG portal at the Association for Computational 135 | Linguistics wiki [3]. 136 | 137 | Last but not least, you might be interested in the author's blog [4] 138 | and the class notes of his recent NLG course [5]. 139 | 140 | 141 | * [2] http://www.siggen.org/ 142 | * [3] http://aclweb.org/aclwiki/index.php?title=Natural_Language_Generation_Portal 143 | * [4] http://duboue.net/blog.html 144 | * [5] http://wiki.duboue.net/index.php/2011_FaMAF_Intro_to_NLG 145 | 146 | ## Integrations 147 | * https://doc.tiki.org/Natural-Language-Generation 148 | 149 | 150 | ## Sponsorship 151 | 152 | Work on NLGen is sponsored by [Textualization Software Ltd.](https://textualization.com). 153 | 154 | 155 | ## License 156 | 157 | This library is licensed under the MIT License - See the [LICENSE](LICENSE) file for details. 158 | 159 | 160 | -------------------------------------------------------------------------------- /ROADMAP: -------------------------------------------------------------------------------- 1 | Some possible future additions 2 | 3 | (if you'd like to try your chops at one of these, just fork the 4 | project at GitHub.) 5 | 6 | 7 | * Add a base lexicon using WordNet 8 | 9 | * More examples so as to complete the complexity continuum 10 | 11 | * A sqlite3 backed lexicon and ontology example 12 | 13 | * Handle backtracking using exceptions 14 | 15 | * Web-based visualization tool for the semantic annotations 16 | 17 | * Complete the Tarot example 18 | 19 | 20 | Pipe dreams 21 | 22 | * Add the Penman Upper Model as an ontology [1] 23 | 24 | * Add a statistical generation component that'll "fill-in-the-blanks" given weaker semantics. 25 | 26 | 27 | [1] http://www.fb10.uni-bremen.de/anglistik/langpro/kpml/um89/um89-root.htm 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nlgen/nlgen", 3 | "type": "library", 4 | "description": "A library for creating recursive-descent natural language generators.", 5 | "homepage": "http://wiki.duboue.net/PHP-NLGen", 6 | "license": "MIT", 7 | "keywords": [ 8 | "artificial intelligence", "nlp", "natural language generation", "nlg", "generation", 9 | "text construction", "grammar", "chatbot", "natural language processing", 10 | "computational linguistics", "recursive descent" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Pablo Duboue", 15 | "email": "pablo.duboue@gmail.com", 16 | "homepage": "http://duboue.net", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=7.4.0" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "^9.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "NLGen\\": "src/" 29 | }, 30 | "exclude-from-classmap": ["/examples/*"] 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "NLGen\\Tests\\": "tests/" 35 | } 36 | }, 37 | "archive" : { 38 | "exclude": ["/examples/*"] 39 | }, 40 | "scripts": { 41 | "compile-grammars": [ 42 | "@php scripts/compileGrammars.php" 43 | ], 44 | "build": [ 45 | "@compile-grammars", 46 | "@test" 47 | ], 48 | "test": "phpunit" 49 | }, 50 | "support": { 51 | "issues": "https://github.com/DrDub/PHP-NLGen/issues", 52 | "source": "https://github.com/DrDub/PHP-NLGen" 53 | }, 54 | "funding": [ 55 | { 56 | "type": "ko-fi", 57 | "url": "https://ko-fi.com/textualization" 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | local_install 2 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # NLGen: Example Generators 2 | 3 | These examples use the library through composer. To use them, do: 4 | 5 | ```bash 6 | cd basic # or bocasucia, etc 7 | composer install 8 | php BasicGenerator1.php 0 0 0 0 # or bocasucia.php or ... 9 | ``` 10 | 11 | If you edit the `composer.json` and remove the `repository` entry, you 12 | can use the nlgen package as available on packagist.org. 13 | 14 | The examples here are intended to show a continuum of increasing complexity. 15 | 16 | The first example (`basic/BasicGenerator1.php`) is a simple event 17 | reporting text nugget, to fit into a larger Web interface. It is 18 | intended to verbalize quadruplets of the form `($agent, $event, 19 | $action, $theme)`, meaning the agent $agent has performed an `$event` 20 | (started, finished, make progress) with respect to the action `$action` 21 | on the topic of `$theme`. 22 | 23 | For example: 24 | 25 | Juan started working on Item 25. 26 | 27 | This is a really, really simple example and you might be hard pressed 28 | to justify using a NLG framework for it. Still, readability of the 29 | source code might justify it, particularly as you try to add more 30 | things to it. 31 | 32 | The next example (`basic/BasicGenerator2.php`) is same as 33 | `BasicGenerator1`, but this time with a lexicon for providing texts 34 | for `$agent` and `$theme`. In a production system you'll expect this 35 | information to be fetched from a DB and a `Lexicon` subclass that can 36 | be tied to a DB is a planned feature (see ROADMAP). 37 | 38 | The last basic example (`basic/BasicGenerator3.php`) uses an ontology 39 | to distinguish between actors that are people and are referred by name 40 | (e.g., _Juan_) versus events referring to automatic tools (e.g., 41 | _"nightly build"_). This information is used to add an article to 42 | form a noun phrase (e.g., _"Juan"_ vs. _"The nightly build"_). While 43 | this might be the simplest possible use of ontological information for 44 | NLG, it might just as well be too simple. 45 | 46 | 47 | A whole section of more complex examples using semantic annotations is 48 | planned to go into a folder medium/ . There we want examples that 49 | should how semantic annotations can simplify dealing with number 50 | agreement and pronominalization. The planned example is a SMS-powered 51 | package-delivering notification system that should generate: 52 | 53 | * _Your two packages were delivered yesterday. The recipient signed 54 | them in._ 55 | 56 | * _Your package was delivered two days ago. The recipient signed it in._ 57 | 58 | Again, simple example, but here you can profit from semantic 59 | annotations to the point of simplifying the code and make it much more 60 | reusable. 61 | 62 | 63 | The next example is a Tarot spread interpreter in the `tarot/` 64 | folder. This is as complex as it can get and currently it can only 65 | produce an opening statement, referring to the overall "goodness" of 66 | the spread and a then generates a statement about the current 67 | situation (cards 1 and 2). 68 | 69 | The ontology only has (vague) information about 11 cards and still can 70 | produce this type of texts: 71 | 72 | _"Ouch. Currently, you got the empress and the lovers. The empress 73 | implies a little bit of a puzzle. This follows the lovers which 74 | implies good things to come. The lovers refers to relationships, 75 | sexuality but also personal beliefs and values."_ 76 | 77 | _"And what is this supposed to mean? Currently, you got the five of 78 | pentacles and the fool. The five of pentacles implies a little bit of 79 | a puzzle. The pentacles are nurturing, concrete. This follows the 80 | fool which implies good things to come."_ 81 | 82 | _"Things seem to be doing well, I would say. Currently, you got the 83 | lovers and the tower. The lovers implies good things to come. The 84 | lovers refers to relationships, sexuality but also personal beliefs 85 | and values. This strongly opposes the tower which implies something 86 | bad."_ 87 | 88 | _"Good, good. Currently, you got the five of pentacles and the ace of 89 | pentacles. The five of pentacles implies a little bit of a puzzle. 90 | The pentacles are nurturing, concrete. This follows the ace of 91 | pentacles which implies good things to come. Same as the other card, 92 | this is also a pentacles."_ 93 | 94 | 95 | An insult generator example is in the `bocasucia/` folder. As this 96 | generator is heavily lexicon-driven, most of the structure lies in the 97 | lexicon rules. It is an example of the 'function' and 'mixed' lexical 98 | capabilities in lexicon.php. The subject matter (insults in river 99 | plate Spanish, with English glosses) might bother certain people. If 100 | you feel this might be your case, please refrain from looking into 101 | this example. This example is also under construction, but much more 102 | functional than the tarot reader (for all the functionality rendered 103 | by a program that just generates insults, that is). 104 | 105 | 106 | Multilingual extensions are showcased in the `multilingual/` folder. 107 | It is a budget modifications commentary generator to go along a 108 | now defunct Web site ([http://budgetplateau.com](http://web.archive.org/web/20111129102920/http://budgetplateau.com/), unrelated to the author). 109 | 110 | The `ste` folder contains Simple Technical English Linearizer built 111 | duing the first NLG hackathon at INLG'16. 112 | 113 | The `webnlg` contains an attempt at the WebNLG 2017 challenge 114 | (unsubmitted). 115 | 116 | The `chatbot` folder contains a small chatbot integration example 117 | presented at the Vancouver PHP Meetup in 2019. 118 | 119 | The `german` folder contains an expansion of the chatbot to the German 120 | language. It handles case and gender agreements (including weak and 121 | strong endings for adjectives). 122 | 123 | The `availability` folder contains example codes (including an 124 | [Elm](https://elm-lang.org/) front-end) for the `AvailibityGenerator` 125 | shipped with NLGen. 126 | -------------------------------------------------------------------------------- /examples/availability/.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | elm.js 3 | demo.png 4 | -------------------------------------------------------------------------------- /examples/availability/Makefile: -------------------------------------------------------------------------------- 1 | index.html: Main.elm elm.json 2 | elm make Main.elm 3 | 4 | ship: 5 | cp index.html,ship index.html 6 | elm make --optimize --output=elm.js Main.elm 7 | uglifyjs elm.js --compress 'pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe' | uglifyjs --mangle --output elm.min.js 8 | 9 | -------------------------------------------------------------------------------- /examples/availability/README.md: -------------------------------------------------------------------------------- 1 | # AvailabilityGenerator 2 | 3 | This generation grammar constructs text descriptions for weekly 4 | schedules. The grammar is ready to use and accessible by importing 5 | `\NLGen\Grammars\Availability\AvailabilityGenerator`. 6 | 7 | The method `generateAvailability` receives a list of "busy times" in 8 | the form of 9 | 10 | `[ day-of-week, [ start hour, start minute ], [ end hour, end minute ] ]` 11 | 12 | a list of ranges indicating when the scheduled day starts and ends (in 13 | the form of `[ day-of-week => [ start hour, start minute ], [ end 14 | hour, end minute ] ]`) and a constant indicating how "coarse" should 15 | be the text (one liner summarizing or very detailed). 16 | 17 | See also `tests/Availability/AvailabilityTest`. 18 | 19 | ## Examples in this folder 20 | 21 | The examples here include: 22 | 23 | * `random.php`, produces a random schedule and shows the generated 24 | text at the four levels of coarseness. Useful for testing but it 25 | doesn't show a lot of the symmetry exploited by the analysis in the 26 | code. 27 | 28 | * `regular.php` a specific schedule with two similar days. 29 | 30 | * `ical.php`, receives an ical file such as the ones that can be 31 | exported from Google Calendar and the monday of the week you want to 32 | generate. Shows the output over the four coarseness levels. 33 | 34 | * `api.php`, this end-point is intended to be used with `Main.elm` to 35 | produce a dynamic visualization of the generator (because [PHP loves Elm](http://wiki.duboue.net/PHP_Elm)). 36 | The provided [Elm](https://elm-lang.org/) front-end allows you to 37 | select busy times and see the output at a selected level of 38 | coarseness. It compiles to `index.html` with the provided `Makefile`. 39 | See the live demo at http://textualization.com/availability/. 40 | 41 | ![PHP-NLGen Availability Demo Screenshot](https://textualization.com/availability/demo.png) 42 | 43 | ## Known issues 44 | 45 | The grammar lacks **opportunistic aggregation**. As such, it might 46 | decide that two blocks are not equal enough to aggregate but then 47 | verbalize them with the same words. That produces redundant text such 48 | as "Tuesday afternoon is mostly free. Wednesday afternoon is mostly 49 | free." 50 | 51 | -------------------------------------------------------------------------------- /examples/availability/api.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | use NLGen\Grammars\Availability\AvailabilityGenerator; 29 | 30 | header('Content-Type: application/json; charset=utf-8'); 31 | 32 | function clean($a) 33 | { 34 | if(! is_array($a)) { 35 | return $a; 36 | } 37 | if(isset($a['value'])) { 38 | return $a['value']; 39 | } 40 | $result=[]; 41 | foreach($a as $k=>$v){ 42 | if($k === "text") { 43 | continue; 44 | } 45 | if($k === "dows" && isset($v['number'])){ 46 | continue; 47 | } 48 | $v = clean($v); 49 | $result[$k] = $v; 50 | if($k === "blocks" && !isset($v[0])) { 51 | $result[$k] = [ $v ]; 52 | } 53 | } 54 | return $result; 55 | } 56 | 57 | if ($_SERVER['REQUEST_METHOD']=="POST") { 58 | $rawData = file_get_contents('php://input'); 59 | $data = json_decode($rawData, True); 60 | //file_put_contents("/tmp/api_call.json", $rawData); 61 | $coarseness = $data[0]; 62 | $ranges = []; 63 | foreach($data[1] as $dow=>$pair) { 64 | $ranges[intval($dow)] = $pair; 65 | } 66 | $busyList = $data[2]; 67 | 68 | $gen = new AvailabilityGenerator(); 69 | $text = $gen->generateAvailability($busyList, $ranges, $coarseness, null); 70 | $sem = $gen->semantics(); 71 | //file_put_contents("/tmp/api_result.json", json_encode($sem)); 72 | 73 | $result = []; 74 | $result['text'] = $text; 75 | $maxCount = count($sem['top']); 76 | $sentCount = 0; 77 | $prev = null; 78 | $delta = 0; 79 | while(isset($sem['top'][$sentCount])) { 80 | if($prev) { // merge with previous for fragments 81 | $delta++; 82 | $sem['top'][$sentCount-$delta] = $sem['top'][$sentCount]; 83 | $sem['top']['offsetStart'] = $prev['offsetStart']; 84 | $prev = null; 85 | }elseif(! isset($sem['top'][$sentCount]['blocks'])){ // fragment 86 | $prev = $sem['top'][$sentCount]; 87 | }elseif($delta){ 88 | $sem['top'][$sentCount - $delta] = $sem['top'][$sentCount]; 89 | } 90 | $sentCount++; 91 | } 92 | if($delta) { 93 | foreach(range($sentCount-$delta, $sentCount-1) as $i) { 94 | unset($sem['top'][$i]); 95 | } 96 | $sentCount -= $delta; 97 | } 98 | //file_put_contents("/tmp/api_result2.json", json_encode($sem)); 99 | $result['sentences'] = []; 100 | foreach(range(0, $sentCount - 1) as $sent) { 101 | //$sentText = substr($text, $sem['top'][$sent]['offsetStart'], $sem['top'][$sent]['offsetEnd'] - $sem['top'][$sent]['offsetStart']); 102 | //if($sent) { 103 | // $key = "focusedMessage$sent"; 104 | //}else{ 105 | // $key = "focusedMessage"; 106 | //} 107 | $sentSem = clean($sem['top'][$sent]); 108 | //$sentSem['offsetStart'] = $sem['top'][$sent]['offsetStart']; 109 | //$sentSem['offsetEnd'] = $sem['top'][$sent]['offsetEnd']; 110 | $result['sentences'][] = $sentSem; 111 | //$sentText = substr($text, $sem['top'][$sent]['offsetStart'], $sem['top'][$sent]['offsetEnd'] - $sem['top'][$sent]['offsetStart']); 112 | } 113 | /* 114 | foreach($sem['top'] as $key => $val) { 115 | echo "Key: ";print_r($key);echo "\n"; 116 | echo "Value: ";print_r($val);echo"\n"; 117 | } 118 | */ 119 | $result['status'] = 200; 120 | 121 | //file_put_contents("/tmp/api_result3.json", json_encode($result)); 122 | echo json_encode($result); 123 | }else{ 124 | echo '{"status":500, "error":"wrong method"}'; 125 | } 126 | 127 | -------------------------------------------------------------------------------- /examples/availability/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nlgen/nlgen-example-availability", 3 | "description": "availability grammar example", 4 | "type": "project", 5 | "keywords": [], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Pablo Duboue", 10 | "email": "pablo.duboue@gmail.com", 11 | "homepage": "http://duboue.net", 12 | "role": "Developer" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=7.4", 17 | "nlgen/nlgen":"*", 18 | "johngrogg/ics-parser": "^3" 19 | }, 20 | "autoload": { 21 | "classmap": [ "." ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/availability/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "." 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/browser": "1.0.2", 10 | "elm/core": "1.0.5", 11 | "elm/html": "1.0.0", 12 | "elm/http": "2.0.0", 13 | "elm/json": "1.1.3", 14 | "elm/time": "1.0.0", 15 | "justinmimbs/date": "3.2.1", 16 | "rtfeldman/elm-css": "18.0.0" 17 | }, 18 | "indirect": { 19 | "elm/bytes": "1.0.8", 20 | "elm/file": "1.0.5", 21 | "elm/parser": "1.1.0", 22 | "elm/url": "1.0.0", 23 | "elm/virtual-dom": "1.0.3", 24 | "robinheghan/murmur3": "1.0.0", 25 | "rtfeldman/elm-hex": "1.0.0" 26 | } 27 | }, 28 | "test-dependencies": { 29 | "direct": {}, 30 | "indirect": {} 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/availability/ical.php: -------------------------------------------------------------------------------- 1 | add(new DateInterval("P6D")); 19 | 20 | try { 21 | $ical = new ICal($argv[1], array( 22 | 'defaultSpan' => 2, // Default value 23 | 'defaultTimeZone' => 'UTC', 24 | 'defaultWeekStart' => 'MO', // Default value 25 | 'disableCharacterReplacement' => false, // Default value 26 | 'filterDaysAfter' => null, // Default value 27 | 'filterDaysBefore' => null, // Default value 28 | 'httpUserAgent' => null, // Default value 29 | 'skipRecurrence' => false, // Default value 30 | )); 31 | } catch (\Exception $e) { 32 | die($e); 33 | } 34 | 35 | $ranges = [0 => [ [6, 0], [24, 0] ], 36 | 1 => [ [6, 0], [24, 0] ], 37 | 2 => [ [6, 0], [24, 0] ], 38 | 3 => [ [6, 0], [24, 0] ], 39 | 4 => [ [6, 0], [24, 0] ], 40 | 5 => [ [6, 0], [24, 0] ], 41 | 6 => [ [6, 0], [24, 0] ]]; 42 | 43 | $busyList = []; 44 | $events = $ical->eventsFromRange($monday->format('Y-m-d H:i:s'), $saturday->format('Y-m-d H:i:s')); 45 | foreach($events as $evt){ 46 | $dtstart = $ical->iCalDateToDateTime($evt->dtstart_array[3]); 47 | $dtend = $ical->iCalDateToDateTime($evt->dtend_array[3]); 48 | #echo $dtstart->format('N H:i') . ' -- ' . $dtend->format('H:i')." -- " . $evt->summary. "\n"; 49 | 50 | [ $dow, $sh, $sm, $eh, $em ] = explode(" ", $dtstart->format('N H i') . ' ' . $dtend->format('H i')); 51 | [ $dow, $sh, $sm, $eh, $em ] = [ intval($dow), intval($sh), intval($sm), intval($eh), intval($em) ]; 52 | if($eh == 0) { 53 | $eh = 24; 54 | $em = 0; 55 | } 56 | if($sh < 6) { 57 | $sh = 6; 58 | } 59 | echo "$dow $sh:$sm -- $eh:$em -- " . $evt->summary. "\n"; 60 | $busyList[] = [ $dow-1, [$sh, $sm], [$eh, $em] ]; 61 | } 62 | #print_r($busyList); 63 | 64 | if(true){ 65 | $gen = new AvailabilityGenerator(); 66 | }else{ 67 | $class = AvailabilityGrammar::class; 68 | $path = realpath($loader->findFile($class)); 69 | $lexicon = file_get_contents(dirname($path)."/lexicon_en.json"); 70 | $gen = AvailabilityGrammar::NewSealed('', $lexicon); 71 | } 72 | 73 | foreach(range(0,3) as $coarseness) { 74 | echo AvailabilityGenerator::COARSENESS[$coarseness].":\n\n"; 75 | $text = $gen->generateAvailability($busyList, $ranges, $coarseness, null); 76 | echo strtoupper(AvailabilityGenerator::COARSENESS[$coarseness])." OUTPUT: $text\n"; 77 | echo "\n------\n"; 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /examples/availability/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Main 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/availability/index.html,ship: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Main 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/availability/random.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | $loader = require __DIR__ . '/vendor/autoload.php'; 27 | 28 | use NLGen\Grammars\Availability\AvailabilityGenerator; 29 | use NLGen\Grammars\Availability\AvailabilityGrammar; 30 | 31 | mt_srand(5); 32 | mt_srand(8); 33 | //mt_srand(9); 34 | 35 | $granularities = [ 10, 15, 30, 60 ]; 36 | $starts = [ 8, 9 ]; 37 | $ends = [ 16, 17, 18 ]; 38 | $dowss = [ range(0,4), range(0,5) ]; 39 | 40 | $gran = $granularities[ mt_rand(0, count($granularities)-1) ]; 41 | $start = $starts[ mt_rand(0, count($starts)-1) ]; 42 | $end = $ends[ mt_rand(0, count($ends)-1) ]; 43 | $dows = $dowss[ mt_rand(0, count($dowss)-1) ]; 44 | 45 | $table=[]; 46 | $busyness = mt_rand() / mt_getrandmax(); 47 | foreach($dows as $dow) { 48 | $time = $start; 49 | $time_m = 0; 50 | $day = []; 51 | while($time < $end) { 52 | $next = $time; 53 | $next_m = $time_m + $gran; 54 | if($next_m >= 60) { 55 | $next += 1; 56 | $next_m = 0; 57 | } 58 | $day[] = [ [ $time, $time_m ], [ $next, $next_m ], mt_rand() / mt_getrandmax() < $busyness ]; 59 | $time = $next; 60 | $time_m = $next_m; 61 | } 62 | $table[$dow] = $day; 63 | } 64 | 65 | $busyList = []; 66 | $ranges = []; 67 | foreach($dows as $dow) { 68 | $previous = null; 69 | foreach($table[$dow] as $seg) { 70 | if($seg[2]) { 71 | if($previous) { // keep growing 72 | }else{ // start 73 | $previous = $seg; 74 | } 75 | }else{ 76 | if($previous) { 77 | $busyList[] = [ $dow, $previous[0], $seg[0] ]; 78 | $previous = null; 79 | } // else, nothing to do 80 | } 81 | } 82 | if($previous){ 83 | $busyList[] = [ $dow, $previous[0], [$end, 0] ]; 84 | } 85 | $ranges[$dow] = [ [$start, 0], [$end, 0] ]; 86 | } 87 | 88 | echo "Granurality: $gran\n"; 89 | echo "Busyness: $busyness\n"; 90 | echo "Ranges: $start-$end\n"; 91 | 92 | $dow = 0; 93 | echo "\n$dow: "; 94 | foreach($busyList as $e) { 95 | if($e[0] != $dow) { 96 | $dow=$e[0]; 97 | echo "\n$dow: "; 98 | } 99 | echo sprintf("%d:%02d",$e[1][0],$e[1][1])."-".sprintf("%d:%02d", $e[2][0],$e[2][1]).", "; 100 | } 101 | echo "\n"; 102 | //$s=var_export($ranges, true); 103 | //echo '$ranges = '.preg_replace('/\s+/', ' ', implode(" ", explode("\n", $s)))."\n"; 104 | //$s=var_export($busyList, true); 105 | //echo '$busyList = '.preg_replace('/\s+/', ' ', implode(" ", explode("\n", $s)))."\n"; 106 | 107 | if(false){ 108 | $gen = new AvailabilityGenerator(); 109 | }else{ 110 | $class = AvailabilityGrammar::class; 111 | $path = realpath($loader->findFile($class)); 112 | $lexicon = file_get_contents(dirname($path)."/lexicon_en.json"); 113 | $gen = AvailabilityGrammar::NewSealed('', $lexicon); 114 | } 115 | 116 | foreach(range(0,3) as $coarseness) { 117 | echo AvailabilityGenerator::COARSENESS[$coarseness].":\n\n"; 118 | $text = $gen->generateAvailability($busyList, $ranges, $coarseness, null); 119 | echo strtoupper(AvailabilityGenerator::COARSENESS[$coarseness])." OUTPUT: $text\n"; 120 | echo "\n------\n"; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /examples/availability/regular.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | $loader = require __DIR__ . '/vendor/autoload.php'; 27 | 28 | use NLGen\Grammars\Availability\AvailabilityGenerator; 29 | use NLGen\Grammars\Availability\AvailabilityGrammar; 30 | 31 | mt_srand(5); 32 | //mt_srand(8); 33 | //mt_srand(9); 34 | 35 | $ranges = [0 => [ [8, 0], [17, 0] ], 36 | 1 => [ [8, 0], [17, 0] ], 37 | 2 => [ [8, 0], [17, 0] ], 38 | 3 => [ [8, 0], [17, 0] ], 39 | 4 => [ [8, 0], [17, 0] ], 40 | 5 => [ [8, 0], [17, 0] ] ]; 41 | 42 | // Monday and Wednesday are the same 43 | // Tuesday and Saturday are similar 44 | 45 | $busyList = [ 46 | [0, [ 8, 00], [ 8, 50]], 47 | [0, [10, 10], [10, 40]], 48 | [0, [11, 30], [11, 40]], 49 | [0, [12, 00], [13, 00]], 50 | [0, [14, 20], [14, 50]], 51 | [0, [15, 30], [16, 00]], 52 | [0, [16, 40], [17, 00]], 53 | [1, [ 8, 10], [ 8, 30]], 54 | [1, [11, 00], [11, 40]], 55 | [1, [12, 00], [12, 30]], 56 | [1, [13, 00], [13, 50]], 57 | [1, [15, 00], [15, 40]], 58 | [1, [15, 50], [16, 50]], 59 | [2, [ 8, 00], [ 8, 50]], 60 | [2, [10, 10], [10, 40]], 61 | [2, [11, 30], [11, 40]], 62 | [2, [12, 00], [13, 00]], 63 | [2, [14, 20], [14, 50]], 64 | [2, [15, 30], [16, 00]], 65 | [2, [16, 40], [17, 00]], 66 | [3, [ 8, 10], [ 8, 40]], 67 | [3, [ 9, 20], [10, 00]], 68 | [3, [10, 20], [10, 40]], 69 | [3, [12, 30], [13, 10]], 70 | [3, [14, 10], [14, 40]], 71 | [4, [ 8, 40], [ 9, 00]], 72 | [4, [10, 00], [10, 30]], 73 | [4, [12, 50], [14, 40]], 74 | [4, [15, 50], [16, 50]], 75 | [5, [ 8, 00], [ 8, 40]], 76 | [5, [11, 00], [11, 50]], 77 | [5, [13, 00], [13, 50]], 78 | [5, [12, 10], [12, 40]], 79 | [5, [15, 10], [15, 50]], 80 | [5, [15, 50], [16, 50]]]; 81 | 82 | 83 | if(true){ 84 | $gen = new AvailabilityGenerator(); 85 | }else{ 86 | $class = AvailabilityGrammar::class; 87 | $path = realpath($loader->findFile($class)); 88 | $lexicon = file_get_contents(dirname($path)."/lexicon_en.json"); 89 | $gen = AvailabilityGrammar::NewSealed('', $lexicon); 90 | } 91 | 92 | foreach(range(0,3) as $coarseness) { 93 | echo AvailabilityGenerator::COARSENESS[$coarseness].":\n\n"; 94 | $text = $gen->generateAvailability($busyList, $ranges, $coarseness, null); 95 | echo strtoupper(AvailabilityGenerator::COARSENESS[$coarseness])." OUTPUT: $text\n"; 96 | echo "\n------\n"; 97 | } 98 | 99 | -------------------------------------------------------------------------------- /examples/basic/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /examples/basic/BasicGenerator1.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | use NLGen\Generator; 29 | 30 | class BasicGenerator1 extends Generator { 31 | 32 | var $agents = array('Juan','Pedro','The helpdesk operator'); 33 | var $events = array('started','is','finished'); 34 | var $actions = array('working on','coding','doing QA on'); 35 | var $themes = array('Component ABC','Item 25','the delivery subsystem'); 36 | 37 | protected function top($data){ 38 | return 39 | $this->person($data[0]). " " . 40 | $this->action($data[1], $data[2]). " " . 41 | $this->item($data[3]); 42 | } 43 | 44 | protected function person($agt){ 45 | return $this->agents[$agt]; 46 | } 47 | 48 | protected function action($evt, $act){ 49 | return $this->events[$evt]." ".$this->actions[$act]; 50 | } 51 | 52 | protected function item($thm){ 53 | return $this->themes[$thm]; 54 | } 55 | } 56 | 57 | global $argv,$argc; 58 | 59 | // execute as php BasicGenerator1.php 0 0 0 0 60 | 61 | if($argc > 1) { 62 | $gen = BasicGenerator1::NewSealed(); 63 | $context = []; 64 | if($argc > 5) { 65 | $context['debug'] = true; 66 | } 67 | print $gen->generate(array_splice($argv,1), $context)."\n"; 68 | 69 | print_r($gen->semantics()); 70 | }else{ 71 | echo BasicGenerator1::Compile()[0]; 72 | } 73 | 74 | -------------------------------------------------------------------------------- /examples/basic/BasicGenerator2.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | use NLGen\Generator; 29 | 30 | class BasicGenerator2 extends Generator { 31 | 32 | protected function top($data){ 33 | return 34 | ucfirst($this->person($data[0]). " " . 35 | $this->action($data[1], $data[2]). " " . 36 | $this->item($data[3])); 37 | } 38 | 39 | protected function person($agent){ 40 | return $this->lex->string_for_id($agent); 41 | } 42 | 43 | protected function action($event, $action){ 44 | return $this->lex->string_for_id($event)." ".$this->lex->string_for_id($action); 45 | } 46 | 47 | protected function item($theme){ 48 | return $this->lex->string_for_id($theme); 49 | } 50 | } 51 | 52 | global $argv,$argc; 53 | 54 | $lexicon_json = << 1) { 77 | $gen = BasicGenerator2::NewSealed('',$lexicon_json); 78 | $context = []; 79 | if($argc > 5) { 80 | $context['debug'] = true; 81 | } 82 | print $gen->generate(array_splice($argv,1), $context)."\n"; 83 | 84 | print_r($gen->semantics()); 85 | }else{ 86 | echo BasicGenerator2::Compile()[0]; 87 | } 88 | -------------------------------------------------------------------------------- /examples/basic/BasicGenerator3.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | use NLGen\Generator; 29 | 30 | class BasicGenerator3 extends Generator { 31 | 32 | function top($data){ 33 | return 34 | ucfirst($this->person($data[0]). " " . 35 | $this->action($data[1], $data[2]). " " . 36 | $this->item($data[3])); 37 | } 38 | 39 | protected function person($agent){ 40 | return 41 | ($this->onto->find_by_path(array($agent, "class", "requires_determiner")) == "yes"?"the ":"") . 42 | $this->lex->string_for_id($agent); 43 | } 44 | 45 | protected function action($event, $action){ 46 | return $this->lex->string_for_id($event)." ".$this->lex->string_for_id($action); 47 | } 48 | 49 | protected function item($theme){ 50 | return 51 | ($this->onto->find_by_path(array($theme, "class", "requires_determiner")) == "yes"?"the ":"") . 52 | $this->lex->string_for_id($theme); 53 | } 54 | } 55 | 56 | global $argv,$argc; 57 | 58 | $lexicon_json = << 1) { 99 | $gen = BasicGenerator3::NewSealed($onto_json,$lexicon_json); 100 | $context = []; 101 | if($argc > 5) { 102 | $context['debug'] = true; 103 | } 104 | print $gen->generate(array_splice($argv,1), $context)."\n"; 105 | 106 | print_r($gen->semantics()); 107 | }else{ 108 | echo BasicGenerator3::Compile()[0]; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # Basic examples for PHP-NLGen 2 | 3 | Try running them with an extra argument for debug output or with no 4 | arguments to see the generated code with the function intercept. 5 | 6 | ## Setup 7 | 8 | ```bash 9 | $ composer install 10 | ``` 11 | 12 | These examples use locally installed package. If you edit the 13 | `composer.json` and remove the `repository` entry, you can use the 14 | nlgen package as available on packagist.org. 15 | 16 | 17 | ## BasicGenerator1 18 | 19 | No lexicon, four semantic inputs. 20 | 21 | ```bash 22 | $ php BasicGenerator1.php 0 0 0 0 23 | 24 | Juan started working on Component ABC 25 | Array 26 | ( 27 | [top] => Array 28 | ( 29 | [person] => Array 30 | ( 31 | [text] => Juan 32 | ) 33 | 34 | [action] => Array 35 | ( 36 | [text] => started working on 37 | ) 38 | 39 | [item] => Array 40 | ( 41 | [text] => Component ABC 42 | ) 43 | 44 | [text] => Juan started working on Component ABC 45 | ) 46 | 47 | ) 48 | ``` 49 | 50 | ## BasicGenerator2 51 | 52 | Uses a lexicon. The lexical entry for "code" has two variants that are 53 | randomly sampled. 54 | 55 | ```bash 56 | php BasicGenerator1.php juan ongoing code itm_25 57 | Juan Perez is doing programming on Item 25 58 | Array 59 | ( 60 | [top] => Array 61 | ( 62 | [person] => Array 63 | ( 64 | [text] => Juan Perez 65 | ) 66 | 67 | [action] => Array 68 | ( 69 | [text] => is doing programming on 70 | ) 71 | 72 | [item] => Array 73 | ( 74 | [text] => Item 25 75 | ) 76 | 77 | [text] => Juan Perez is doing programming on Item 25 78 | ) 79 | 80 | ) 81 | ``` 82 | 83 | ## BasicGenerator3 84 | 85 | Now with a small ontology to decide whether to use an article (THE 86 | helpdesk operator) vs. proper nouns that require no article. In the 87 | previous example, the common nouns were already present with articles 88 | in the lexicon, which diminishes its utility. 89 | 90 | ```bash 91 | $ php BasicGenerator3.php helpdesk finish qa sub_delivery 92 | The helpdesk operator finished doing QA on the delivery subsystem 93 | Array 94 | ( 95 | [top] => Array 96 | ( 97 | [person] => Array 98 | ( 99 | [text] => the helpdesk operator 100 | ) 101 | 102 | [action] => Array 103 | ( 104 | [text] => finished doing QA on 105 | ) 106 | 107 | [item] => Array 108 | ( 109 | [text] => the delivery subsystem 110 | ) 111 | 112 | [text] => The helpdesk operator finished doing QA on the delivery subsystem 113 | ) 114 | 115 | ) 116 | ``` 117 | -------------------------------------------------------------------------------- /examples/basic/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nlgen/nlgen-example-basic", 3 | "description": "basic NLG example", 4 | "type": "project", 5 | "keywords": [], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Pablo Duboue", 10 | "email": "pablo.duboue@gmail.com", 11 | "homepage": "http://duboue.net", 12 | "role": "Developer" 13 | } 14 | ], 15 | "repositories" : [ 16 | { 17 | "type":"package", 18 | "package" : { 19 | "name":"nlgen/nlgen", 20 | "version":"0.1", 21 | "dist":{ 22 | "url":"../local_install/nlgen.zip", 23 | "type":"zip" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "NLGen\\": "src/" 28 | } 29 | } 30 | } 31 | } 32 | ], 33 | "require": { 34 | "php": ">=7.2", 35 | "nlgen/nlgen":"*" 36 | }, 37 | "autoload": { 38 | }, 39 | "scripts": { 40 | "archive-package": [ 41 | "mkdir -p ../local_install", 42 | "@composer archive --working-dir=../.. --dir=examples/local_install --file=nlgen --format=zip" 43 | ], 44 | "pre-install-cmd": "@archive-package", 45 | "pre-update-cmd": "@archive-package" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/bocasucia/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /examples/bocasucia/bocasucia.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | $ontology = file_get_contents("ontology.json"); 29 | $lexicon = file_get_contents("lexicon.json"); 30 | 31 | $gen = new BocaSuciaGenerator($ontology, $lexicon); 32 | 33 | # inputs 34 | 35 | # possible constraints 36 | 37 | # length 38 | # ultra-short (a short insult) 39 | # short (one long or a few short) 40 | # default (the full markov model) 41 | 42 | # taboo-unlock 43 | # yes / true / 1 (implies gay, racist and sexual violent insults) 44 | # 0 (default) 45 | 46 | # level 47 | # polite 48 | # normal (default) 49 | # nasty (includes normal) 50 | 51 | # speaker, target 52 | # sm (default, single male) 53 | # sf (single female) 54 | # pm (plural male) 55 | # pf (plural female) 56 | 57 | # seed 58 | # a number to seed the random generator, default is to use a random one, but fixed for the class itself. 59 | 60 | # other constraints are insult types, in all caps and a probability associated with them 61 | # they will override the defaults. For full access, the taboo unlock has to be fired. 62 | 63 | global $argv,$argc; 64 | 65 | $constraints = array(); 66 | for ($i = 1; $i < $argc; $i+=2) { 67 | $key = $argv[$i]; 68 | if(substr($key,0,1) == "-"){ 69 | $key=substr($key,1); 70 | } 71 | $constraints[$key] = $argv[$i+1]; 72 | } 73 | 74 | if(!isset($constraints['seed'])){ 75 | $constraints['seed'] = rand(); 76 | print $constraints['seed']. " "; 77 | } 78 | 79 | print $gen->generate($constraints)."\n"; 80 | 81 | if(isset($constraints['show_sem'])){ 82 | print_r($gen->semantics()); 83 | } 84 | if(isset($constraints['show_gloss'])){ 85 | show_gloss($gen->semantics(),""); 86 | } 87 | 88 | function show_gloss($array,$indent){ 89 | if(isset($array['text'])){ 90 | if(strlen($array['text'])>0){ 91 | print $indent.$array['text']."\n"; 92 | } 93 | } 94 | if(isset($array['gloss'])){ 95 | print $indent." Gloss: ".$array['gloss']."\n"; 96 | } 97 | foreach($array as $value){ 98 | if(is_array($value)){ 99 | show_gloss($value, "$indent "); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/bocasucia/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nlgen/nlgen-example-bocasucia", 3 | "description": "Spanish insult generator NLG example", 4 | "type": "project", 5 | "keywords": [], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Pablo Duboue", 10 | "email": "pablo.duboue@gmail.com", 11 | "homepage": "http://duboue.net", 12 | "role": "Developer" 13 | } 14 | ], 15 | "repositories" : [ 16 | { 17 | "type":"package", 18 | "package" : { 19 | "name":"nlgen/nlgen", 20 | "version":"0.1", 21 | "dist":{ 22 | "url":"../local_install/nlgen.zip", 23 | "type":"zip" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "NLGen\\": "src/" 28 | } 29 | } 30 | } 31 | } 32 | ], 33 | "require": { 34 | "php": ">=7.2", 35 | "nlgen/nlgen":"*" 36 | }, 37 | "autoload": { 38 | "classmap": [ "." ] 39 | }, 40 | "scripts": { 41 | "archive-package": [ 42 | "mkdir -p ../local_install", 43 | "@composer archive --working-dir=../.. --dir=examples/local_install --file=nlgen --format=zip" 44 | ], 45 | "pre-install-cmd": "@archive-package", 46 | "pre-update-cmd": "@archive-package" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/bocasucia/ontology.json: -------------------------------------------------------------------------------- 1 | { 2 | "INSULT-TYPE" : { "class" : "insult_type", 3 | "description" : "general insult" 4 | }, 5 | "WISH" : { "class" : "insult_type", "parent" : "INSULT-TYPE", 6 | "description" : "wish somebody something" 7 | }, 8 | "WISH-GO" : { "class" : "insult_type", "parent" : "WISH", 9 | "description" : "Wish they go to a place, either real or figurative" 10 | }, 11 | "WISH-DO" : { "class" : "insult_type", "parent" : "WISH", 12 | "description" : "wish they go and do something, usually unpleasant" 13 | }, 14 | "WISH-BAD" : { "class" : "insult_type", "parent" : "WISH", 15 | "description" : "wish something bad happens to them" 16 | }, 17 | "WISH-STOP" : { "class" : "insult_type", "parent" : "WISH", 18 | "description" : "wish they stop doing something" 19 | }, 20 | "DESCRIBE" : { "class" : "insult_type", "parent" : "INSULT-TYPE", 21 | "description" : "refer to specific qualities of the person" 22 | }, 23 | "BAD" : { "class" : "insult_type", "parent" : "DESCRIBE", 24 | "description" : "bad person" 25 | }, 26 | "HOMO" : { "class" : "insult_type", "parent" : "DESCRIBE", 27 | "taboo-lock" : "true", 28 | "description" : "of non-conformant sexual tendencies" 29 | }, 30 | "BAD-SEED" : { "class" : "insult_type", "parent" : "DESCRIBE", 31 | "description" : "of an undesirable ascendance" 32 | }, 33 | "LOWLY" : { "class" : "insult_type", "parent" : "DESCRIBE", 34 | "description" : "of an undesirable ascendance" 35 | }, 36 | "ANIMAL" : { "class" : "insult_type", "parent" : "DESCRIBE", 37 | "description" : "of animal-like characteristics" 38 | }, 39 | "BAD-PHYSIQUE" : { "class" : "insult_type", "parent" : "DESCRIBE", 40 | "description" : "of undesirable physical characteristics" 41 | }, 42 | "BAD-HISTORY" : { "class" : "insult_type", "parent" : "DESCRIBE", 43 | "description" : "negative events or attitudes in their past history" 44 | }, 45 | "UNINTELLIGENT" : { "class" : "insult_type", "parent" : "DESCRIBE", 46 | "description" : "lack of intellectual prowess" 47 | }, 48 | "UNFORTUNATE" : { "class" : "insult_type", "parent" : "DESCRIBE", 49 | "description" : "marked by ill fortune" 50 | }, 51 | "LACKING" : { "class" : "insult_type", "parent" : "DESCRIBE", 52 | "description" : "marked by unsatisfied needs, usually sexual" 53 | }, 54 | "THREATEN" : { "class" : "insult_type", "parent" : "INSULT-TYPE", 55 | "description" : "threaten the person" 56 | }, 57 | "THREATEN-PHYSICAL" : { "class" : "insult_type", "parent" : "THREATEN", 58 | "description" : "threaten to use physical violence" 59 | }, 60 | "THREATEN-SEX" : { "class" : "insult_type", "parent" : "THREATEN", 61 | "description" : "threaten to use sexual violence" 62 | }, 63 | "EQUATE" : { "class" : "insult_type", "parent" : "INSULT-TYPE", 64 | "description" : "equate the person to an object of undesirable characteristics" 65 | }, 66 | "EQUATE-BODY-PART" : { "class" : "insult_type", "parent" : "EQUATE", 67 | "description" : "equate the person to body parts" 68 | }, 69 | "EQUATE-BODY-WASTE" : { "class" : "insult_type", "parent" : "EQUATE", 70 | "description" : "equate the person to body excretions" 71 | }, 72 | "EQUATE-OBJECT" : { "class" : "insult_type", "parent" : "EQUATE", 73 | "description" : "equate the person to inanimate objects with undesirable characteristics" 74 | }, 75 | "INTERJ" : { "class" : "insult_type", "parent" : "INSULT-TYPE", 76 | "description" : "general interjections" 77 | }, 78 | 79 | "LENGTH" : { "class" : "insult_length" }, 80 | "LENGTH-SHORT" : { "class" : "insult_length", "parent" : "LENGTH" }, 81 | "LENGTH-MEDIUM" : { "class" : "insult_length", "parent" : "LENGTH" }, 82 | "LENGTH-LONG" : { "class" : "insult_length", "parent" : "LENGTH" }, 83 | 84 | "LEVEL" : { "class" : "insult_level" }, 85 | "POLITE" : { "class" : "insult_level", "parent" : "LEVEL" }, 86 | "NORMAL" : { "class" : "insult_level", "parent" : "LEVEL" }, 87 | "NASTY" : { "class" : "insult_level", "parent" : "LEVEL" } 88 | 89 | } -------------------------------------------------------------------------------- /examples/chatbot/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /examples/chatbot/README.md: -------------------------------------------------------------------------------- 1 | # Botman NLGen Example 2 | 3 | A small example for the Vancouver PHP Meetup lighting talk. 4 | 5 | To run the example: 6 | 7 | composer install 8 | 9 | php -S 127.0.0.1:8080 10 | 11 | open http://127.0.0.1:8080 on your browser. 12 | 13 | Note this example uses NLGen from packagist, unlike the other examples. 14 | 15 | -------------------------------------------------------------------------------- /examples/chatbot/TruckConfigGenerator.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | use NLGen\Generator; 29 | 30 | class TruckConfigGenerator extends Generator { 31 | 32 | # data are different dimensions changed plus new values 33 | function top($data){ 34 | # check and remove defaults 35 | $info = array(); 36 | if($data['box'] == 'short'){ 37 | $info['box'] = 'short'; 38 | } 39 | if($data['cabin'] == 'crew'){ 40 | $info['cabin'] = 'crew'; 41 | } 42 | if($data['drivetrain'] == '4x4'){ 43 | $info['drivetrain'] = '4x4'; 44 | } 45 | 46 | return $this->sentence($data['action'], $info); 47 | } 48 | 49 | protected function sentence($action, $info) { 50 | $result = "You want to " . $action . " " . $this->truck($info) . '.'; 51 | $sem = $this->current_semantics()['truck_orig']; 52 | if(isset($sem['leftover'])){ 53 | $result = $result . " " . $this->truck_s($sem['leftover']); 54 | } 55 | return $result; 56 | } 57 | 58 | protected function truck($data) { 59 | if(count($data) == 3){ 60 | return array('text' => "a " . $this->box_ap($data['box']) . " pickup truck " . $this->cabin_pp($data['cabin']), 61 | 'sem' => array('leftover' => array('drivetrain' => $data['drivetrain']))); 62 | } 63 | if(isset($data['box'])){ 64 | $result = "a " . $this->box_ap($data['box']) . " pickup truck"; 65 | if(isset($data['cabin'])) { 66 | $result = $result . " " . $this->cabin_pp($data['cabin']); 67 | }elseif(isset($data['drivetrain'])) { 68 | $result = $result . " " . $this->drivetrain_pp($data['drivetrain']); 69 | } 70 | return $result; 71 | } 72 | if(isset($data['cabin'])){ 73 | $result = "a " . $this->cabin_ap($data['cabin']) . " pickup truck"; 74 | if(isset($data['drivetrain'])) { 75 | return array('text' => $result, 76 | 'sem' => array('leftover' => array('drivetrain' => $data['drivetrain']))); 77 | }else{ 78 | return $result; 79 | } 80 | } 81 | if(isset($data['drivetrain'])){ 82 | return "a " . $this->drivetrain_ap($data['drivetrain']) . " pickup truck"; 83 | } 84 | return "a pickup truck"; 85 | } 86 | 87 | protected function cabin_ap($cabin) { 88 | return $cabin . "-cabin"; 89 | } 90 | protected function cabin_pp($cabin) { 91 | return "with a " . $cabin . " cabin"; 92 | } 93 | 94 | protected function drivetrain_ap($drivetrain) { 95 | if($drivetrain == "4x4"){ 96 | return "four wheel-drive"; 97 | } 98 | return "standard traction"; 99 | } 100 | protected function drivetrain_pp($drivetrain) { 101 | return "with " . $this->drivetrain_ap($drivetrain); 102 | } 103 | 104 | protected function box_ap($box) { 105 | return $box . "-box"; 106 | } 107 | protected function box_pp($box) { 108 | return "having a " . $box . " box"; 109 | } 110 | 111 | protected function truck_s($data) { 112 | if(isset($data['cabin'])){ 113 | return "It comes " . $this->cabin_pp($data['cabin']) . "."; 114 | } 115 | if(isset($data['drivetrain'])) { 116 | return "It is a " . $this->drivetrain_ap($data['drivetrain']) . "."; 117 | } 118 | if(isset($data['box'])) { 119 | return "It has a " . $data['box'] . " box."; 120 | } 121 | return ""; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/chatbot/botman_example.php: -------------------------------------------------------------------------------- 1 | hears('turd', function (BotMan $bot) { 21 | 22 | $ontology = file_get_contents("../bocasucia/ontology.json"); 23 | $lexicon = file_get_contents("../bocasucia/lexicon.json"); 24 | 25 | $gen = new BocaSuciaGenerator($ontology, $lexicon); 26 | 27 | $bot->reply($gen->generate(array( 'level' => 'polite' ))); 28 | }); 29 | 30 | $botman->hears('hello', function (BotMan $bot) { 31 | $bot->reply('Hello there. Ask me to sell you a pickup truck.'); 32 | }); 33 | 34 | $botman->hears('Sell me a pickup truck.', function (BotMan $bot) { 35 | $bot->userStorage()->save([ 'state' => 'action' ]); 36 | $bot->reply('Do you want to buy, finance or lease?'); 37 | }); 38 | 39 | $botman->hears('((buy)|(finance)|(lease))', function (BotMan $bot, $action) { 40 | if($bot->userStorage()->get('state') != 'action') { 41 | $bot->reply('Say "hello"'); 42 | }else{ 43 | $bot->userStorage()->save([ 'state' => 'drivetrain', 'action' => $action ]); 44 | $bot->reply('It comes standard with 4x2 drivetrain, do you want a 4x4 one?'); 45 | } 46 | }); 47 | 48 | $botman->hears('((yes)|(no))', function (BotMan $bot, $ans) { 49 | $state = $bot->userStorage()->get('state'); 50 | if($state == "drivetrain"){ 51 | $drivetrain = $ans == "yes" ? "4x4" : "4x2"; 52 | $bot->userStorage()->save([ 'state' => 'box', 'drivetrain' => $drivetrain ]); 53 | $bot->reply('It comes standard with a long box (6\'4"). Do you want a short box (5\'7")?'); 54 | }elseif($state == "box"){ 55 | $box = $ans == "yes" ? "short" : "long"; 56 | $bot->userStorage()->save([ 'state' => 'cabin', 'box' => $box ]); 57 | $bot->reply('Do you want a quad or crew cabin? The standard is the quad cabin.'); 58 | }else{ 59 | $bot->reply('Say "hello"'); 60 | } 61 | }); 62 | 63 | 64 | $botman->hears('(4x(4|2))', function (BotMan $bot, $drivetrain) { 65 | if($bot->userStorage()->get('state') != 'drivetrain') { 66 | $bot->reply('Say "hello"'); 67 | }else{ 68 | $bot->userStorage()->save([ 'state' => 'box', 'drivetrain' => $drivetrain ]); 69 | $bot->reply('It comes standard with a long box (6\'4"). Do you want a short box (5\'7")?'); 70 | } 71 | }); 72 | 73 | $botman->hears('((long)|(short))', function (BotMan $bot, $box) { 74 | if($bot->userStorage()->get('state') != 'box') { 75 | $bot->reply('Say "hello"'); 76 | }else{ 77 | $bot->userStorage()->save([ 'state' => 'cabin', 'box' => $box ]); 78 | $bot->reply('Do you want a quad or crew cabin?'); 79 | } 80 | }); 81 | 82 | $botman->hears('((quad)|(crew))', function (BotMan $bot, $cab) { 83 | if($bot->userStorage()->get('state') != 'cabin') { 84 | $bot->reply('Say "hello"'); 85 | }else{ 86 | $gen = TruckConfigGenerator::NewSealed(); 87 | 88 | $bot->reply($gen->generate([ 89 | 'action' => $bot->userStorage()->get('action'), 90 | 'drivetrain' => $bot->userStorage()->get('drivetrain'), 91 | 'box' => $bot->userStorage()->get('box'), 92 | 'cabin' => $cab ])); 93 | } 94 | }); 95 | 96 | 97 | // Start listening 98 | $botman->listen(); 99 | -------------------------------------------------------------------------------- /examples/chatbot/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PHP-NLGen 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/chatbot/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nlgen/nlgen-example-botman", 3 | "description": "Example generation grammars with botman", 4 | "type": "project", 5 | "keywords": [], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Pablo Duboue", 10 | "email": "pablo.duboue@gmail.com", 11 | "homepage": "http://duboue.net", 12 | "role": "Developer" 13 | } 14 | ], 15 | "require": { 16 | "botman/botman": "^2.4", 17 | "botman/driver-web": "^1.5", 18 | "nlgen/nlgen" : ">0.11" 19 | }, 20 | "autoload": { 21 | "classmap": [ "." ] 22 | }, 23 | "minimum-stability" : "dev" 24 | } 25 | -------------------------------------------------------------------------------- /examples/chatbot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PHP-NLGen Demo 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/german/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /examples/german/ConfigurationConversation.php: -------------------------------------------------------------------------------- 1 | $lexicon_en, 'de' => $lexicon_de ] ); 14 | $GLOBALS['gen'] = $gen; 15 | 16 | class ConfigurationConversation extends Conversation { 17 | 18 | protected $gen; 19 | protected $lang; 20 | protected $config; 21 | 22 | public function __construct($lang) { 23 | $this->gen = $GLOBALS['gen']; 24 | $this->lang = $lang; 25 | $this->config = []; 26 | } 27 | 28 | public function run() { 29 | $this->askNext("vehicle"); 30 | } 31 | 32 | public function askNext($type) { 33 | $question = $this->generateQuestion($type); 34 | //error_log($question); 35 | $sem = $this->gen->semantics()['top']['question']; 36 | $this->ask($question, function(Answer $answer) use ($sem, $type) { 37 | $answer_text = $answer->getText(); 38 | //error_log("Received: " . $answer_text); 39 | $correct = false; 40 | if(isset($sem['default'])) { 41 | # yes / no 42 | if(strtolower($answer_text) == $this->gen->lex->string_for_id('yes')) { 43 | $this->config[$type] = $sem['other']['onto']; 44 | }else{ 45 | $this->config[$type] = $sem['default']['onto']; 46 | } 47 | $correct = true; 48 | }else{ 49 | # find valid answers and match it 50 | foreach($sem['opts'] as $opt){ 51 | //error_log(" Sem: " . $opt['string']); 52 | if(strtolower($answer_text) == strtolower($opt['string'])) { 53 | $this->config[$type] = $opt['onto']; 54 | $correct = true; 55 | } 56 | } 57 | } 58 | if($correct){ 59 | $type = $this->nextQuestion($type); 60 | if($type) { 61 | $this->askNext($type); 62 | }else{ 63 | $this->say($this->gen->generate($this->config, [ 'lang' => $this->lang ])); 64 | } 65 | }else{ 66 | $this->askNext($type); 67 | } 68 | }); 69 | } 70 | 71 | protected function generateQuestion($question) { 72 | $data = [ 'type' => 'question', 'need' => $question]; 73 | if(isset($this->config['vehicle'])) { 74 | $data['target'] = $this->config['vehicle']; 75 | } 76 | return $this->gen->generate($data, [ 'lang' => $this->lang ]); 77 | } 78 | 79 | protected function nextQuestion($question) { 80 | //error_log("In next question $question: " . print_r($this->config, TRUE)); 81 | $current = $this->gen->onto->find($question); 82 | foreach($this->gen->onto->find_all_by_class('question') as $id) { 83 | $frame = $this->gen->onto->find($id); 84 | if($frame['order'] == $current['order'] + 1){ 85 | $applies = true; 86 | if(isset($frame['applies'])){ 87 | foreach($frame['applies'] as $key => $value) { 88 | if(! isset($this->config[$key]) || $this->config[$key] != $value){ 89 | $applies = false; 90 | } 91 | } 92 | } 93 | if($applies){ 94 | return $id; 95 | } 96 | } 97 | } 98 | return NULL; 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /examples/german/README.md: -------------------------------------------------------------------------------- 1 | # German NLGen Example 2 | 3 | A small example for the S&S Media PHP Magazin article [Textgenerierungsgrammatik mit NLGen](https://entwickler.de/machine-learning/natural-language-generation-php). 4 | 5 | To run the example: 6 | 7 | composer install 8 | 9 | php -S 127.0.0.1:8080 10 | 11 | open http://127.0.0.1:8080 on your browser. (You can also say 'hello' to run the dialog in English.) 12 | 13 | Note this example uses NLGen from packagist, unlike the other examples. 14 | 15 | 16 | Run php run_all.php to obtain all the possible strings that can be generated (see generated.txt). 17 | 18 | 19 | ## About the code 20 | 21 | This is one of the most complex examples of NLGen to date, as it 22 | handles case and gender agreements (including weak and strong endings 23 | for adjectives). 24 | 25 | The code was written following German grammatic information from 26 | various online sources but the author doesn't speak any German. If you 27 | can speak German and see errors, please file a bug report. 28 | 29 | 30 | ## Example dialog 31 | 32 | * Botman: Sag ,,hallo'' 33 | * User: hallo 34 | 35 | * Botman: Hallo. Was willst du? (Boot oder Pickup) 36 | * User: boot 37 | 38 | * Botman: Sie wollen kaufen, finanzieren oder leasen? 39 | * User: kaufen 40 | 41 | * Botman: Es ist standardmäßig mit einem Außenbordmotor ausgestattet. Möchten Sie einen Innenbordmotor? 42 | * User: ja 43 | 44 | * Botman: Es ist standardmäßig mit einer langen Ladefläche ausgestattet. Möchten Sie eine kurze Ladefläche? 45 | * User: ja 46 | 47 | * Botman: Es ist standardmäßig mit einer Viererkabine ausgestattet. Möchten Sie eine Mannschaftskabine? 48 | * User: ja 49 | 50 | * Botman: Die Standardfarbe ist weiß. Bevorzugen Sie ein rotes Boot? 51 | * User: ja 52 | 53 | * Botman: Sie möchten ein rotes Boot mit kurzer Ladefläche, Mannschaftskabine und einem Innenbordmotor kaufen. 54 | -------------------------------------------------------------------------------- /examples/german/botman_example.php: -------------------------------------------------------------------------------- 1 | hears('hello', function (BotMan $bot) { 21 | $bot->startConversation(new ConfigurationConversation("en")); 22 | }); 23 | 24 | $botman->hears('hallo', function (BotMan $bot) { 25 | $bot->startConversation(new ConfigurationConversation("de")); 26 | }); 27 | 28 | // Start listening 29 | $botman->listen(); 30 | -------------------------------------------------------------------------------- /examples/german/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PHP-NLGen 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/german/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nlgen/nlgen-example-multilingual-botman", 3 | "description": "Example multilingual generation grammars with botman", 4 | "type": "project", 5 | "keywords": [], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Pablo Duboue", 10 | "email": "pablo.duboue@gmail.com", 11 | "homepage": "http://duboue.net", 12 | "role": "Developer" 13 | } 14 | ], 15 | "require": { 16 | "botman/botman": "^2.4", 17 | "botman/driver-web": "^1.5", 18 | "nlgen/nlgen" : ">0.15" 19 | }, 20 | "autoload": { 21 | "classmap": [ "." ] 22 | }, 23 | "minimum-stability" : "dev" 24 | } 25 | -------------------------------------------------------------------------------- /examples/german/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PHP-NLGen Demo 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/german/lexicon_de.json: -------------------------------------------------------------------------------- 1 | { "greet" : { "class" : "phrase", "string" : "Hallo." } 2 | , "reset" : { "class" : "phrase", "string" : "Sag ,,hallo''" } 3 | , "q_vehicle" : { "class" : "phrase", "mixed" : [ 4 | "Was möchten Sie? (", { "slot" : "options" }, ")" ] } 5 | , "q_action" : { "class" : "phrase", "mixed" : [ 6 | "Wollen Sie ", { "slot" : "options" }, "?" ] } 7 | , "standard" : { "class" : "phrase", "mixed" : [ 8 | "Es ist standardmäßig mit ", { "slot" : "option" }, " ausgestattet." ] } 9 | , "altern" : { "class" : "phrase", "mixed" : [ 10 | "Möchten Sie ", { "slot" : "option" }, "?" ] } 11 | , "standardc" : { "class" : "phrase", "mixed" : [ 12 | "Die Standardfarbe ist ", { "slot" : "color" }, "." ] } 13 | , "alternc" : { "class" : "phrase", "mixed" : [ 14 | "Bevorzugen Sie ", { "slot" : "colorobj" }, "?" ] } 15 | , "config" : { "class" : "phrase", "mixed" : [ 16 | "Sie möchten ", { "slot" : "configured" }, " ", { "slot" : "action" }, "." ] } 17 | 18 | , "yes" : { "class" : "answer", "string" : "ja" } 19 | , "no" : { "class" : "answer", "string" : "nein" } 20 | , "buy" : { "class" : "answer", "string" : "kaufen" } 21 | , "finance" : { "class" : "answer", "string" : "finanzieren" } 22 | , "lease" : { "class" : "answer", "string" : "leasen" } 23 | , "quad" : { "class" : "answer", "string" : "viererkabine" } 24 | , "crew" : { "class" : "answer", "string" : "mannschaftskabine" } 25 | , "inboard" : { "class" : "answer", "string" : "innenbordmotor" } 26 | , "outboard" : { "class" : "answer", "string" : "außenbordmotor" } 27 | 28 | , "opt_truck" : { "class" : "option", "string" : "Pickup", "gender" : "m", "onto" : "truck" } 29 | , "opt_boat" : { "class" : "option", "string" : "Boot", "gender" : "n", "onto" : "boat" } 30 | , "opt_inboard" : { "class" : "option", "string" : "Innenbordmotor", "gender" : "m", "onto" : "inboard" } 31 | , "opt_outboard": { "class" : "option", "string" : "Außenbordmotor", "gender" : "m", "onto" : "outboard" } 32 | , "opt_4x4" : { "class" : "option", "string" : "4x4-Antrieb", "gender" : "m", "onto" : "4x4" } 33 | , "opt_4x2" : { "class" : "option", "string" : "4x2-Antrieb", "gender" : "m", "onto" : "4x2" } 34 | , "opt_short" : { "class" : "option", "string" : "kurze Ladefläche", "gender" : "f", "onto" : "short" } 35 | , "opt_long" : { "class" : "option", "string" : "langen Ladefläche", "gender" : "f", "onto" : "long" } 36 | , "opt_quad" : { "class" : "option", "string" : "Viererkabine", "gender" : "f", "onto" : "quad" } 37 | , "opt_crew" : { "class" : "option", "string" : "Mannschaftskabine", "gender" : "f", "onto" : "crew" } 38 | , "opt_red" : { "class" : "option", "string" : "roter", "gender" : "m", "onto" : "red" } 39 | , "opt_white" : { "class" : "option", "string" : "weißer", "gender" : "m", "onto" : "white" } 40 | 41 | , "truck" : { "class" : "noun", "string" : "Pickup", "gender" : "m" } 42 | , "boat" : { "class" : "noun", "string" : "Boot", "gender" : "n" } 43 | , "box" : { "class" : "noun", "string" : "Ladefläche", "gender" : "f" } 44 | 45 | , "or" : { "class" : "conj", "string" : "oder" } 46 | , "and" : { "class" : "conj", "string" : "und" } 47 | , "with" : { "class" : "prep", "string" : "mit", "case": "dat" } 48 | 49 | , "short" : [ 50 | { "class" : "adj", "string" : "kurz", "root" : "y" } 51 | , { "class" : "adj", "string" : "kurze", "gender" : "f", "case" : "nom", "ending" : "mixed" } 52 | , { "class" : "adj", "string" : "kurzer", "gender" : "f", "case" : "dat", "ending" : "strong" } 53 | , { "class" : "adj", "string" : "kurzen", "gender" : "f", "case" : "dat", "ending" : "weak" } 54 | ] 55 | , "long" : [ 56 | { "class" : "adj", "string" : "lang", "root" : "y" } 57 | , { "class" : "adj", "string" : "lange", "gender" : "f", "case" : "nom", "ending" : "mixed" } 58 | , { "class" : "adj", "string" : "langer", "gender" : "f", "case" : "dat", "ending" : "strong" } 59 | , { "class" : "adj", "string" : "langen", "gender" : "f", "case" : "dat", "ending" : "weak" } 60 | ] 61 | , "white" : [ 62 | { "class" : "adj", "string" : "weiß", "root" : "y" } 63 | , { "class" : "adj", "string" : "weißer", "gender" : "m", "case" : "nom", "ending" : "strong" } 64 | , { "class" : "adj", "string" : "weiße", "gender" : "m", "case" : "nom", "ending" : "weak" } 65 | , { "class" : "adj", "string" : "weiße", "gender" : "n", "case" : "nom", "ending" : "weak" } 66 | , { "class" : "adj", "string" : "weißes", "gender" : "n", "case" : "nom", "ending" : "weak" } 67 | , { "class" : "adj", "string" : "weiße", "gender" : "f", "case" : "nom", "ending" : "mixed" } 68 | , { "class" : "adj", "string" : "weißen", "gender" : "m", "case" : "dat", "ending" : "mixed" } 69 | , { "class" : "adj", "string" : "weißen", "gender" : "n", "case" : "dat", "ending" : "mixed" } 70 | , { "class" : "adj", "string" : "weißer", "gender" : "f", "case" : "dat", "ending" : "strong" } 71 | , { "class" : "adj", "string" : "weißen", "gender" : "f", "case" : "dat", "ending" : "weak" } 72 | , { "class" : "adj", "string" : "weißen", "gender" : "m", "case" : "acc", "ending" : "weak" } 73 | , { "class" : "adj", "string" : "weißes", "gender" : "n", "case" : "acc", "ending" : "strong" } 74 | , { "class" : "adj", "string" : "weiße", "gender" : "n", "case" : "acc", "ending" : "weak" } 75 | , { "class" : "adj", "string" : "weiße", "gender" : "f", "case" : "acc", "ending" : "mixed" } 76 | ] 77 | , "red" : [ 78 | { "class" : "adj", "string" : "rot", "root" : "y" } 79 | , { "class" : "adj", "string" : "roter", "gender" : "m", "case" : "nom", "ending" : "strong" } 80 | , { "class" : "adj", "string" : "rote", "gender" : "m", "case" : "nom", "ending" : "weak" } 81 | , { "class" : "adj", "string" : "rotes", "gender" : "n", "case" : "nom", "ending" : "strong" } 82 | , { "class" : "adj", "string" : "rote", "gender" : "n", "case" : "nom", "ending" : "weak" } 83 | , { "class" : "adj", "string" : "rote", "gender" : "f", "case" : "nom", "ending" : "mixed" } 84 | , { "class" : "adj", "string" : "roter", "gender" : "m", "case" : "dat", "ending" : "strong" } 85 | , { "class" : "adj", "string" : "roten", "gender" : "m", "case" : "dat", "ending" : "weak" } 86 | , { "class" : "adj", "string" : "roten", "gender" : "n", "case" : "dat", "ending" : "mixed" } 87 | , { "class" : "adj", "string" : "roter", "gender" : "f", "case" : "dat", "ending" : "strong" } 88 | , { "class" : "adj", "string" : "roten", "gender" : "f", "case" : "dat", "ending" : "weak" } 89 | , { "class" : "adj", "string" : "roten", "gender" : "m", "case" : "acc", "ending" : "weak" } 90 | , { "class" : "adj", "string" : "rotes", "gender" : "n", "case" : "acc", "ending" : "strong" } 91 | , { "class" : "adj", "string" : "rote", "gender" : "n", "case" : "acc", "ending" : "weak" } 92 | , { "class" : "adj", "string" : "rote", "gender" : "f", "case" : "acc", "ending" : "mixed" } 93 | ] 94 | 95 | , "det_indef" : [ 96 | { "class" : "det", "string" : "ein", "gender" : "m", "case" : "nom", "ending" : "weak", "def": "n" } 97 | , { "class" : "det", "string" : "einen", "gender" : "m", "case" : "acc", "ending" : "weak", "def": "n" } 98 | , { "class" : "det", "string" : "eines", "gender" : "m", "case" : "gen", "ending" : "strong", "def": "n" } 99 | , { "class" : "det", "string" : "einem", "gender" : "m", "case" : "dat", "ending" : "strong", "def": "n" } 100 | , { "class" : "det", "string" : "eine", "gender" : "f", "case" : "nom", "ending" : "weak", "def": "n" } 101 | , { "class" : "det", "string" : "eine", "gender" : "f", "case" : "acc", "ending" : "weak", "def": "n" } 102 | , { "class" : "det", "string" : "einer", "gender" : "f", "case" : "gen", "ending" : "strong", "def": "n" } 103 | , { "class" : "det", "string" : "einer", "gender" : "f", "case" : "dat", "ending" : "strong", "def": "n" } 104 | , { "class" : "det", "string" : "ein", "gender" : "n", "case" : "nom", "ending" : "weak", "def": "n" } 105 | , { "class" : "det", "string" : "ein", "gender" : "n", "case" : "acc", "ending" : "weak", "def": "n" } 106 | , { "class" : "det", "string" : "eines", "gender" : "n", "case" : "gen", "ending" : "strong", "def": "n" } 107 | , { "class" : "det", "string" : "einem", "gender" : "n", "case" : "dat", "ending" : "strong", "def": "n" } 108 | ] 109 | 110 | } 111 | -------------------------------------------------------------------------------- /examples/german/lexicon_en.json: -------------------------------------------------------------------------------- 1 | { "greet" : { "class" : "phrase", "string" : "Hello there." } 2 | , "reset" : { "class" : "phrase", "string" : "Say \"hello\"" } 3 | , "q_vehicle" : { "class" : "phrase", "mixed" : [ 4 | "What is it that interests you? (", { "slot" : "options" }, ")" ] } 5 | , "q_action" : { "class" : "phrase", "mixed" : [ 6 | "Do you want to ", { "slot" : "options" }, "?" ] } 7 | , "standard" : { "class" : "phrase", "mixed" : [ 8 | "It comes standard ", { "slot" : "option" }, "." ] } 9 | , "altern" : { "class" : "phrase", "mixed" : [ 10 | "Do you want it ", { "slot" : "option" }, "?" ] } 11 | , "standardc" : { "class" : "phrase", "mixed" : [ 12 | "It comes standard in ", { "slot" : "color" }, " color." ] } 13 | , "alternc" : { "class" : "phrase", "mixed" : [ 14 | "Do you want ", { "slot" : "colorobj" }, "?" ] } 15 | , "standardd" : { "class" : "phrase", "mixed" : [ 16 | "It comes standard as ", { "slot" : "option" }, "." ] } 17 | , "config" : { "class" : "phrase", "mixed" : [ 18 | "You want to ", { "slot" : "action" }, " ", { "slot" : "configured" }, "." ] } 19 | , "yes" : { "class" : "answer", "string" : "yes" } 20 | , "no" : { "class" : "answer", "string" : "no" } 21 | , "buy" : { "class" : "answer", "string" : "buy" } 22 | , "finance" : { "class" : "answer", "string" : "finance" } 23 | , "lease" : { "class" : "answer", "string" : "lease" } 24 | , "long" : { "class" : "answer", "string" : "long" } 25 | , "short" : { "class" : "answer", "string" : "short" } 26 | , "quad" : { "class" : "answer", "string" : "quad" } 27 | , "crew" : { "class" : "answer", "string" : "crew" } 28 | , "red" : { "class" : "adj", "string" : "red" } 29 | , "white" : { "class" : "adj", "string" : "white" } 30 | , "box" : { "class" : "noun", "string" : "box" } 31 | , "truck" : { "class" : "noun", "string" : "pickup truck" } 32 | , "boat" : { "class" : "noun", "string" : "boat" } 33 | , "inboard" : { "class" : "phrase", "string" : "inboard engine" } 34 | , "outboard" : { "class" : "phrase", "string" : "outboard engine" } 35 | , "or" : { "class" : "conj", "string" : "or" } 36 | , "and" : { "class" : "conj", "string" : "and" } 37 | , "4x2" : { "class" : "phrase", "string" : "2WD" } 38 | , "4x4" : { "class" : "phrase", "string" : "four-wheel drive" } 39 | , "red" : { "class" : "adj", "string" : "red" } 40 | , "white" : { "class" : "adj", "string" : "white" } 41 | } 42 | -------------------------------------------------------------------------------- /examples/german/ontology.json: -------------------------------------------------------------------------------- 1 | { "vehicle" : { "class" : "question", "order" : 0 } 2 | , "action" : { "class" : "question", "order" : 1 } 3 | , "engine" : { "class" : "question", "order" : 2, "applies" : { "vehicle" : "boat" } } 4 | , "drivetrain" : { "class" : "question", "order" : 2, "applies" : { "vehicle" : "truck" } } 5 | , "box" : { "class" : "question", "order" : 3 } 6 | , "cabin" : { "class" : "question", "order" : 4 } 7 | , "color" : { "class" : "question", "order" : 5 } 8 | 9 | , "boat" : { "class" : "option", "question" : "vehicle" } 10 | , "truck" : { "class" : "option", "question" : "vehicle" } 11 | 12 | , "buy" : { "class" : "option", "question" : "action" } 13 | , "finance" : { "class" : "option", "question" : "action" } 14 | , "lease" : { "class" : "option", "question" : "action" } 15 | 16 | , "inboard" : { "class" : "option", "question" : "engine" } 17 | , "outboard" : { "class" : "option", "question" : "engine", "default" : true } 18 | 19 | , "4x2" : { "class" : "option", "question" : "drivetrain", "default" : true } 20 | , "4x4" : { "class" : "option", "question" : "drivetrain" } 21 | 22 | , "short" : { "class" : "option", "question" : "box" } 23 | , "long" : { "class" : "option", "question" : "box", "default" : true } 24 | 25 | , "crew" : { "class" : "option", "question" : "cabin"} 26 | , "quad" : { "class" : "option", "question" : "cabin", "default" : true } 27 | 28 | , "white" : { "class" : "option", "question" : "color", "default" : true } 29 | , "red" : { "class" : "option", "question" : "color" } 30 | } 31 | -------------------------------------------------------------------------------- /examples/german/run_all.php: -------------------------------------------------------------------------------- 1 | $lexicon_en, 'de' => $lexicon_de ], false ); 11 | 12 | #print_r($gen->mlex['de']); 13 | 14 | foreach(['en', 'de'] as $lang) { 15 | 16 | foreach(['buy','finance','lease'] as $action) { 17 | foreach(['4x4','4x2'] as $drivetrain) { 18 | foreach(['short','long'] as $box) { 19 | foreach(['quad','crew'] as $cabin) { 20 | foreach(['white','red'] as $color) { 21 | if($INPUTS) echo "$lang truck $action $drivetrain $box $cabin $color\n"; 22 | echo "\t" . $gen->generate([ 23 | 'type' => 'sentence', 24 | 'vehicle' => 'truck', 25 | 'action' => $action, 26 | 'color' => $color, 27 | 'drivetrain' => $drivetrain, 28 | 'box' => $box, 29 | 'cabin' => $cabin ], [ 'lang' => $lang ]) . "\n"; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | foreach(['buy','finance','lease'] as $action) { 36 | foreach(['inboard','outboard'] as $engine) { 37 | foreach(['short','long'] as $box) { 38 | foreach(['quad','crew'] as $cabin) { 39 | foreach(['white','red'] as $color) { 40 | if($INPUTS) echo "$lang boat $action $drivetrain $box $cabin $color\n"; 41 | echo "\t" . $gen->generate([ 42 | 'type' => 'sentence', 43 | 'vehicle' => 'boat', 44 | 'action' => $action, 45 | 'color' => $color, 46 | 'engine' => $engine, 47 | 'box' => $box, 48 | 'cabin' => $cabin ], [ 'lang' => $lang ]) . "\n"; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | foreach(["vehicle","action","engine","drivetrain","box","cabin","color"] as $question) { 56 | foreach(["truck", "boat"] as $vehicle) { 57 | if($INPUTS) echo "$lang $vehicle $question\n"; 58 | echo "\t" . $gen->generate([ 59 | 'type' => 'question', 60 | 'need' => $question, 61 | 'target' => $vehicle ], [ 'lang' => $lang ]) . "\n"; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/multilingual/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /examples/multilingual/BudgetCommentaryGenerator.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | use NLGen\Generator; 29 | 30 | class BudgetCommentaryGenerator extends Generator { 31 | 32 | # data are different dimensions changed plus new values 33 | function top($data){ 34 | # apply the rules and obtain predicates 35 | $preds = $this->apply_rules($data); 36 | 37 | # sort by predicate 38 | $preds = $this->sort_predicates($preds); 39 | 40 | 41 | # aggregate, remove duplicates, subsumed 42 | $preds = $this->sentence_planning($preds); 43 | 44 | #print_r($preds); 45 | 46 | # verbalize 47 | $result = ""; 48 | foreach($preds as $key => $pred) { 49 | $result = $result . ucfirst(trim($this->gen($pred->predicate, $pred->args))) . ". "; 50 | } 51 | 52 | return $result; 53 | } 54 | 55 | function apply_rules($data){ 56 | $result = array(); 57 | 58 | include 'rules.php'; 59 | 60 | return $result; 61 | } 62 | 63 | function sort_predicates($preds){ 64 | $pred_array = array(); 65 | $args_array = array(); 66 | foreach ($preds as $pred) { 67 | $pred_array[] = $pred->predicate; 68 | $args_array[] = join(",", $pred->args); 69 | } 70 | array_multisort($pred_array, $args_array, $preds); 71 | return $preds; 72 | } 73 | 74 | function sentence_planning($preds){ 75 | $new_preds = array(); 76 | 77 | # remove subsumed 78 | $prev = null; 79 | foreach ($preds as $pred) { 80 | if(!is_null($prev)){ 81 | if($this->subsumed($pred,$prev)){ 82 | $new_preds[count($new_preds) - 1] = $pred; 83 | $prev = $pred; 84 | }else if($this->subsumed($prev,$pred)){ 85 | # ignored 86 | }else{ 87 | $new_preds[] = $pred; 88 | $prev = $pred; 89 | } 90 | }else{ 91 | $new_preds[] = $pred; 92 | $prev = $pred; 93 | } 94 | } 95 | 96 | # aggregate 97 | $preds = $new_preds; 98 | $new_preds = array(); 99 | $prev = null; 100 | foreach ($preds as $pred) { 101 | if(!is_null($prev)){ 102 | if($pred->predicate == $prev->predicate && $pred->predicate == 'on_strike'){ 103 | # other predicates TODO 104 | if(is_array($prev->args[0])){ 105 | $arr = $prev->args[0]; 106 | $arr[] = $pred->args[0]; 107 | $prev = new Predicate($prev->predicate, array($arr)); 108 | }else{ 109 | $prev = new Predicate($prev->predicate, 110 | array(array($prev->args[0], $pred->args[0]))); 111 | } 112 | $new_preds[count($new_preds) - 1] = $prev; 113 | }else{ 114 | $new_preds[] = $pred; 115 | $prev = $pred; 116 | } 117 | }else{ 118 | $new_preds[] = $pred; 119 | $prev = $pred; 120 | } 121 | } 122 | 123 | return $new_preds; 124 | } 125 | 126 | function subsumed($gen,$spec){ 127 | if($gen->predicate != $spec->predicate){ 128 | return false; 129 | } 130 | if($gen->predicate != 'benchmarked') { 131 | return false; 132 | } 133 | if($gen->args[0] != $spec->args[0]) { 134 | return false; 135 | } 136 | if($gen->args[1] != $spec->args[1]) { 137 | return false; 138 | } 139 | $frame = $this->onto->find($gen->args[2]); 140 | while(isset($frame['includes'])){ 141 | if($frame['includes'] == $spec->args[2]){ 142 | return true; 143 | } 144 | $frame=$this->onto->find($frame['includes']); 145 | } 146 | return false; 147 | } 148 | 149 | function on_strike($data){ 150 | $actor = $data[0]; 151 | $actor_str = $this->gen("np", array('head'=>$actor),'actor'); 152 | $sem = $this->current_semantics(); 153 | 154 | return $actor_str . ' ' . 155 | $this->gen('on_strike_vp', array('subject' => $sem['actor'])); 156 | } 157 | 158 | function benchmarked_en($data){ 159 | $position = $data[0]; $metric = $data[1]; $region = $data[2]; 160 | return $this->gen('metric',$metric) . " will be the " . 161 | $this->lex->string_for_id($position) . " within the " . 162 | $this->lex->string_for_id($region); 163 | } 164 | 165 | function benchmarked_fr($data){ 166 | $position = $data[0]; 167 | $metric_str = $this->gen('metric',$data[1], 'metric'); $region = $data[2]; 168 | $frame = $this->lex->find('will_be'); 169 | $sem = $this->current_semantics(); 170 | return $metric_str . ' ' . $frame[$sem['metric']['num']] . " les plus " . 171 | $this->lex->string_for_id($position) . " dans la " . 172 | $this->lex->string_for_id($region); 173 | } 174 | 175 | function pct_change_en($data){ 176 | $metric = $data[0]; $delta = $data[1]; 177 | $str = $this->gen('metric',$metric) . " will "; 178 | if($delta > 0) { 179 | $str = $str . "increase by " . $delta . " percent"; 180 | }else{ 181 | $str = $str . "decrease by " . abs($delta) . " percent"; 182 | } 183 | return $str; 184 | } 185 | 186 | function pct_change_fr($data){ 187 | $metric_str = $this->gen('metric', $data[0], 'metric'); $delta = $data[1]; 188 | if($delta > 0) { 189 | $frame = $this->lex->find('will_increase'); 190 | } else { 191 | $frame = $this->lex->find('will_decrease'); 192 | $delta = abs($delta); 193 | } 194 | $sem = $this->current_semantics(); 195 | return $metric_str . ' ' . $frame[$sem['metric']['num']] . ' de ' . $delta . ' pour cent'; 196 | } 197 | 198 | function np($data){ 199 | $head = $data['head']; 200 | #print_r($head); 201 | if(gettype($head) == "object"){ 202 | $str = $this->gen($head->predicate,$head->args,'subpred'); 203 | $sem = $this->current_semantics(); 204 | return array('text'=>$str, 'sem'=>$sem['subpred']); 205 | }else if(gettype($head) == "array"){ 206 | $gen = 'fem'; 207 | $str = array(); 208 | for($i=0;$igen('np', array('head'=>$head[$i]), $subnp); 211 | $sem = $this->current_semantics(); 212 | if($sem[$subnp]['gen'] != 'fem'){ 213 | $gen = 'masc'; 214 | } 215 | } 216 | return array('text'=>join(", ", array_slice($str, 0, count($str)-1)) . ' ' . 217 | $this->lex->string_for_id('conjunction') . ' ' . $str[count($str)-1], 218 | 'sem' => array('gen'=>$gen, 'num'=>'pl')); 219 | }else if($this->lex->has($head)){ 220 | return array('text'=>$this->lex->string($head,$data), 'sem'=>$data); 221 | }else{ 222 | return array('text'=>$head, 'sem'=>array('gen'=>'masc', 'num'=>'sing')); 223 | } 224 | } 225 | 226 | function on_strike_vp($data){ 227 | return $this->lex->string_for_id('on_strike'); 228 | } 229 | 230 | function employee_en($place){ 231 | $employees = $this->lex->string_for_id('employees'); 232 | $place_frame = $this->lex->find($place[0]); 233 | $place = $this->lex->string($place_frame); 234 | if(isset($place_frame['is_short'])){ 235 | return array('text'=> 'the ' . $place . ' ' . $employees, 236 | 'sem'=>array('gen'=>'masc', 'num'=>'pl')); 237 | }else{ 238 | return array('text'=> 'the ' . $employees . ' at the ' . $place, 239 | 'sem'=>array('gen'=>'masc', 'num'=>'pl')); 240 | } 241 | } 242 | 243 | function metric_en($metric){ 244 | return "the " . $this->lex->string_for_id($metric); 245 | } 246 | 247 | function metric_fr($metric){ 248 | $frame = $this->lex->find($metric); 249 | return array('text'=>$this->lex->string($frame),'sem'=>array('num'=>$frame['num'])); 250 | } 251 | 252 | function employee_fr($place){ 253 | $place = $this->lex->string_for_id($place[0]); 254 | return array('text'=>'les ' . $this->lex->string_for_id('employees'). ' de ' . $place, 255 | 'sem'=>array('gen'=>'masc', 'num'=>'pl')); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /examples/multilingual/Predicate.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | class Predicate { 29 | var $predicate; 30 | var $args; 31 | function __construct($pred, $args=array()) { 32 | $this->predicate = $pred; 33 | $this->args = $args; 34 | } 35 | function __toString() { 36 | return $this->predicate . '(' . join(",", $this->args) . ')'; 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /examples/multilingual/README.md: -------------------------------------------------------------------------------- 1 | # Multilingual NLG in NLGen 2 | 3 | This demo showcases NLGen multilingual extensions. It is a budget 4 | modifications commentary generator that originally went with the now 5 | defunct [http://budgetplateau.com](http://web.archive.org/web/20111129102920/http://budgetplateau.com/) website (see 6 | a [post-mortem](https://opennorth.ca/2012/11/citizen-budget-results-from-plateau-mont-royal/)). 7 | This example was done independently from that site and it is not 8 | affiliated with them in any way. The original site allowed people to 9 | experiment with budget changes. The example code produces fictional 10 | potential reactions to the budget changes, both in English and French. 11 | The example code shows how to deal with multiple lexicons and how 12 | functions can be reused across languages or split for specific ones, 13 | as necessary. 14 | 15 | To run it: 16 | 17 | ```bash 18 | composer install 19 | cat sample.json | php budget_commentary.php 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /examples/multilingual/budget_commentary.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | $ontology = file_get_contents("ontology.json"); 29 | $lexicon_en = file_get_contents("lexicon_en.json"); 30 | $lexicon_fr = file_get_contents("lexicon_fr.json"); 31 | 32 | $gen = new BudgetCommentaryGenerator($ontology, 33 | array('en' => $lexicon_en, 'fr' => $lexicon_fr)); 34 | 35 | $json_text = file_get_contents("php://stdin"); 36 | $data = json_decode($json_text,TRUE); 37 | 38 | print_r($data); 39 | $copy = $data; 40 | 41 | print $gen->generate($data, array('lang'=>'fr'))."\n\n"; 42 | print $gen->generate($copy, array('lang'=>'en'))."\n\n"; 43 | -------------------------------------------------------------------------------- /examples/multilingual/budgetplateau_commentary.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name BudgetPlateau commentary 3 | // @namespace ca.duboue 4 | // @description Budget Commentary Generator 5 | // @include http://budgetplateau.com/ 6 | // ==/UserScript== 7 | 8 | function pad_commentary_update(){ 9 | // collect the json file to send 10 | 11 | var elems = [ "culture_feries", "culture_dimanche", "culture_spectacles", "baignade_interieures", 12 | "deneigement_chargements", "deneigement_findesemaine", "routier_nidsdepoule" ]; 13 | 14 | var json = "{ "; 15 | for(var i=0;i'); 44 | } 45 | 46 | padbox = document.createElement('ul'); 47 | padbox.style.position = 'fixed'; 48 | padbox.style.top = '10px'; 49 | padbox.style.left = '10px'; 50 | padbox.style.padding = '20px'; 51 | padbox.style.backgroundColor = '#ccc'; 52 | button = document.createElement('input'); 53 | button.type = 'button'; 54 | button.value = 'commentary'; 55 | button.addEventListener('click',pad_commentary_update); 56 | padbox.appendChild(button); 57 | padbox.appendChild(document.createElement('br')); 58 | radio0 = document.createElement('input'); 59 | radio0.type="radio"; 60 | radio0.name="padlang"; 61 | radio0.value="en"; 62 | radio0.checked=1; 63 | padbox.appendChild(radio0); 64 | padbox.appendChild(document.createTextNode("English")); 65 | radio1 = document.createElement('input'); 66 | radio1.type="radio"; 67 | radio1.name="padlang"; 68 | radio1.value="fr"; 69 | padbox.appendChild(radio1); 70 | padbox.appendChild(document.createTextNode("Français")); 71 | div = document.createElement('div'); 72 | div.id = "padcommentary"; 73 | padbox.appendChild(document.createElement('br')); 74 | padbox.appendChild(div); 75 | body = document.getElementsByTagName('body')[0]; 76 | body.appendChild(padbox); 77 | -------------------------------------------------------------------------------- /examples/multilingual/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nlgen/nlgen-example-multilingual", 3 | "description": "Multilingual generator example", 4 | "type": "project", 5 | "keywords": [], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Pablo Duboue", 10 | "email": "pablo.duboue@gmail.com", 11 | "homepage": "http://duboue.net", 12 | "role": "Developer" 13 | } 14 | ], 15 | "repositories" : [ 16 | { 17 | "type":"package", 18 | "package" : { 19 | "name":"nlgen/nlgen", 20 | "version":"0.1", 21 | "dist":{ 22 | "url":"../local_install/nlgen.zip", 23 | "type":"zip" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "NLGen\\": "src/" 28 | } 29 | } 30 | } 31 | } 32 | ], 33 | "require": { 34 | "php": ">=7.2", 35 | "nlgen/nlgen":"*" 36 | }, 37 | "autoload": { 38 | "classmap": [ "." ] 39 | }, 40 | "scripts": { 41 | "archive-package": [ 42 | "mkdir -p ../local_install", 43 | "@composer archive --working-dir=../.. --dir=examples/local_install --file=nlgen --format=zip" 44 | ], 45 | "pre-install-cmd": "@archive-package", 46 | "pre-update-cmd": "@archive-package" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/multilingual/driver.php: -------------------------------------------------------------------------------- 1 | $lexicon_en, 'fr' => $lexicon_fr)); 11 | 12 | $json_text = file_get_contents("php://input"); 13 | $data = json_decode($json_text,TRUE); 14 | 15 | $copy = $data; 16 | 17 | header('Content-type: text/plain; charset=UTF-8'); 18 | $lang = 'en'; 19 | if($_GET['lang'] == 'fr'){ 20 | $lang = 'fr'; 21 | } 22 | 23 | echo $gen->generate($data, array('lang'=>$lang))."\n"; 24 | -------------------------------------------------------------------------------- /examples/multilingual/example.rules: -------------------------------------------------------------------------------- 1 | IF culture_feries > 2 AND culture_dimanche == 52 THEN 2 | pct_change(reading, 10) 3 | 4 | IF culture_feries + culture_dimanche < 30 THEN 5 | pct_change(reading, -10) 6 | 7 | IF culture_spectacles < 50 THEN 8 | on_strike(employee(maison_culture)) 9 | 10 | IF culture_spectacles < 100 THEN 11 | benchmarked(bottom, culture, city) 12 | 13 | IF culture_spectacles > 140 THEN 14 | benchmarked(top, culture, city) 15 | 16 | IF culture_spectacles > 160 THEN 17 | benchmarked(top, culture, province) 18 | 19 | IF baignade_interieures < -500 THEN 20 | on_strike(employee(heated_pool)) 21 | 22 | IF baignade_interieures > 500 THEN 23 | benchmarked(top, sport, city) 24 | 25 | IF deneigement_chargements == 5 THEN 26 | pct_change(car_accident, 5) 27 | 28 | IF deneigement_chargements == 7 THEN 29 | benchmarked(top, snow_removal, province) 30 | 31 | IF deneigement_chargements == 5 AND deneigement_findesemaine < 3 THEN 32 | pct_change(car_accident, 10) 33 | 34 | IF routier_nidsdepoule > 10 THEN 35 | pct_change(car_accident, -5) 36 | 37 | IF routier_nidsdepoule > 20 THEN 38 | benchmarked(top, street_care, city) 39 | 40 | IF routier_nidsdepoule > 25 THEN 41 | benchmarked(top, street_care, province) 42 | -------------------------------------------------------------------------------- /examples/multilingual/lexicon_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "employees" : { "string" : "employees", "class" : "actor" }, 3 | "conjunction" : { "string" : "and", "class" : "conj" }, 4 | "on_strike" : { "string" : "will go on strike", "class" : "phrase" }, 5 | "maison_culture" : { "string" : "Maison de la Culture", "class" : "place" }, 6 | "heated_pool" : { "string" : "heated pool", "is_short" : "1", "class" : "place" }, 7 | "top" : { "string" : "highest", "class" : "position" }, 8 | "bottom" : { "string" : "lowest", "class" : "position" }, 9 | "city" : { "string" : "city", "class" : "region" }, 10 | "province" : { "string" : "province", "class" : "region" }, 11 | "reading" : { "string" : "reading of printed material", "class" : "metric" }, 12 | "sports" : { "string" : "performance of sport activities", "class" : "metric" }, 13 | "culture" : { "string" : "spending in cultural activities", "class" : "metric" }, 14 | "snow_removal" : { "string" : "spending in snow removal", "class" : "metric" }, 15 | "street_care" : { "string" : "spending in street care", "class" : "metric" }, 16 | "car_accident" : { "string" : "number of car accidents", "class" : "metric" } 17 | } -------------------------------------------------------------------------------- /examples/multilingual/lexicon_fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "employees" : { "string" : "fonctionaires", "class" : "actor" }, 3 | "conjunction" : { "string" : "et" , "class" : "conj" }, 4 | "on_strike" : { "string" : "iront en grève", "class" : "phrase" }, 5 | "will_be" : { "string" : "seront", "pl":"seront", "sing":"sera", "class" : "verb" }, 6 | "will_increase" : { "string" : "augmenteront", "pl":"augmenteront", 7 | "sing":"va augmenter", "class" : "verb" }, 8 | "will_decrease" : { "string" : "diminueront", "pl":"diminueront", 9 | "sing":"diminuera", "class" : "verb" }, 10 | "maison_culture" : { "string" : "la Maison de la Culture", "class" : "place" }, 11 | "heated_pool" : { "string" : "la piscine intérieure", "class" : "place" }, 12 | "top" : { "string" : "élevée", "class" : "position" }, 13 | "bottom" : { "string" : "bas", "class" : "position" }, 14 | "city" : { "string" : "ville", "class" : "region" }, 15 | "province" : { "string" : "province", "class" : "region" }, 16 | "reading" : { "string" : "la lecture", "class" : "metric", "num" : "sing" }, 17 | "sports" : { "string" : "les activités sportives", "class" : "metric", "num" : "pl" }, 18 | "culture" : { "string" : "les dépenses en activités culturelles", "class" : "metric", "num" : "pl" }, 19 | "snow_removal" : { "string" : "les dépenses de déneigement", "class" : "metric", "num" : "pl" }, 20 | "street_care" : { "string" : "les dépenses dans les soins de la rue", "class" : "metric", "num" : "pl" }, 21 | "car_accident" : { "string" : "le nombre d'accidents de voiture", "class" : "metric", "num" : "sing" } 22 | } 23 | -------------------------------------------------------------------------------- /examples/multilingual/ontology.json: -------------------------------------------------------------------------------- 1 | { 2 | "reading" : { "class": "metric" }, 3 | "culture": { "class": "metric" }, 4 | "sport": { "class": "metric" }, 5 | "car_accident": { "class": "metric" }, 6 | "snow_removal": { "class": "metric" }, 7 | "street_care": { "class": "metric" }, 8 | "maison_culture": { "class": "place" }, 9 | "heated_pool": { "class": "place" }, 10 | "top": { "class": "position" }, 11 | "bottom": { "class": "position" }, 12 | "city" : { "class" : "region" }, 13 | "province" : { "class" : "region", "includes" : "city" } 14 | } -------------------------------------------------------------------------------- /examples/multilingual/rules.php: -------------------------------------------------------------------------------- 1 | 2 && $data["culture_dimanche"] == 52 ){ 3 | $result[] = new Predicate("pct_change", array("reading", "10")); 4 | } 5 | if( $data["culture_feries"] + $data["culture_dimanche"] < 30 ){ 6 | $result[] = new Predicate("pct_change", array("reading", "-10")); 7 | } 8 | if( $data["culture_spectacles"] < 50 ){ 9 | $result[] = new Predicate("on_strike", array(new Predicate("employee", array("maison_culture")))); 10 | } 11 | if( $data["culture_spectacles"] < 100 ){ 12 | $result[] = new Predicate("benchmarked", array("bottom", "culture", "city")); 13 | } 14 | if( $data["culture_spectacles"] > 140 ){ 15 | $result[] = new Predicate("benchmarked", array("top", "culture", "city")); 16 | } 17 | if( $data["culture_spectacles"] > 160 ){ 18 | $result[] = new Predicate("benchmarked", array("top", "culture", "province")); 19 | } 20 | if( $data["baignade_interieures"] < -500 ){ 21 | $result[] = new Predicate("on_strike", array(new Predicate("employee", array("heated_pool")))); 22 | } 23 | if( $data["baignade_interieures"] > 500 ){ 24 | $result[] = new Predicate("benchmarked", array("top", "sport", "city")); 25 | } 26 | if( $data["deneigement_chargements"] == 5 ){ 27 | $result[] = new Predicate("pct_change", array("car_accident", "5")); 28 | } 29 | if( $data["deneigement_chargements"] == 7 ){ 30 | $result[] = new Predicate("benchmarked", array("top", "snow_removal", "province")); 31 | } 32 | if( $data["deneigement_chargements"] == 5 && $data["deneigement_findesemaine"] < 3 ){ 33 | $result[] = new Predicate("pct_change", array("car_accident", "10")); 34 | } 35 | if( $data["routier_nidsdepoule"] > 10 ){ 36 | $result[] = new Predicate("pct_change", array("car_accident", "-5")); 37 | } 38 | if( $data["routier_nidsdepoule"] > 20 ){ 39 | $result[] = new Predicate("benchmarked", array("top", "street_care", "city")); 40 | } 41 | if( $data["routier_nidsdepoule"] > 25 ){ 42 | $result[] = new Predicate("benchmarked", array("top", "street_care", "province")); 43 | } 44 | -------------------------------------------------------------------------------- /examples/multilingual/rules2php.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | echo '=7.2", 36 | "nlgen/nlgen":"*" 37 | }, 38 | "scripts": { 39 | "archive-package": [ 40 | "mkdir -p ../local_install", 41 | "@composer archive --working-dir=../.. --dir=examples/local_install --file=nlgen --format=zip" 42 | ], 43 | "pre-install-cmd": "@archive-package", 44 | "pre-update-cmd": "@archive-package" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/ste/example.php: -------------------------------------------------------------------------------- 1 | 1){ 138 | $result[] = array($parts[0], $parts[1], $parts[2]); 139 | } 140 | } 141 | return $result; 142 | } 143 | 144 | 145 | $gen = SimpleTechnicalEnglish::NewSealed(); 146 | 147 | if($argc>1 && $argv[1] != "test") { 148 | // load file 149 | $lines = file($argv[1]); 150 | $deps = array(); 151 | $current = ""; 152 | foreach($lines as $line) { 153 | if($line == "\n"){ 154 | $deps[] = $current; 155 | $current = ""; 156 | }else{ 157 | $current .= $line; 158 | } 159 | } 160 | foreach($deps as $task){ 161 | print ucfirst($gen->generate(parse_deps($task))).".\n"; 162 | } 163 | }else{ 164 | foreach($examples as $example){ 165 | print ucfirst($gen->generate(parse_deps($example))).".\n"; 166 | } 167 | } 168 | //print_r($gen->semantics()); 169 | 170 | 171 | -------------------------------------------------------------------------------- /examples/ste/sentence.deps: -------------------------------------------------------------------------------- 1 | root(ROOT, generates) 2 | det(sentence, a) 3 | dobj(generates, sentence) 4 | det(library, the) 5 | amod(library, nlgen) 6 | nsubj(generates, library) 7 | 8 | -------------------------------------------------------------------------------- /examples/tarot/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /examples/tarot/TarotGenerator.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | use NLGen\Generator; 29 | 30 | class TarotGenerator extends Generator { 31 | # data are the 10 cards of the spread, in order 32 | function top($data){ 33 | # do some analysis and set-up tone in $this->context 34 | 35 | $overall = 36 | 0.5 * ($this->omen($data[0]) + $this->omen($data[1]) * 0.75) + 37 | ($this->omen($data[3])<0?1.0:-0.25) + 1.3*$this->omen($data[5]) + 38 | 1.3 * $this->omen($data[2]) + $this->omen($data[4]) + 39 | 0.5 * $this->omen($data[6]) + $this->omen($data[7]) + 40 | $this->omen($data[8]) + 41 | 5 * $this->omen($data[9]); 42 | 43 | $this->context['overall_score'] = $overall; 44 | $very = abs($overall) > 5 ? 1 : 0; 45 | 46 | $overall_str = $overall == 0? "neutral" : 47 | (($very?"very_":"") . 48 | $overall >= 0? "positive" : "negative"); 49 | $this->context['overall'] = $overall_str; 50 | 51 | return array('text' => 52 | $this->gen("opening", $overall_str) . "\n" . 53 | $this->gen("issue", array($data[0], $data[1])) . "\n" . 54 | $this->gen("time", array($data[3], $data[5])) . "\n" . 55 | $this->gen("consciousness", array($data[2], $data[4])) . "\n". 56 | $this->gen("perception", array($data[6], $data[7])) . "\n". 57 | $this->gen("hope_fears", array($data[8])) . "\n". 58 | $this->gen("outcome", array($data[9])) . "\n", 59 | 'sem'=>array('type'=>'full')); 60 | } 61 | 62 | function opening($overall){ 63 | return array('text' => 64 | $this->lex->string_for_id("opening_" . $overall), 65 | 'sem'=>array('type'=>'opening', 66 | 'overall'=>$overall)); 67 | } 68 | 69 | function issue($data){ 70 | # need to do the generation first, so as to reason about the generated text 71 | $this_sem=&$this->semantics[count($this->semantics)-1]; 72 | $card0 = $this->gen("card", $data[0], 'card0'); 73 | $card1 = $this->gen("card", $data[1], 'card1'); 74 | $card0_issue = $this->gen("issue_card", $data[0],'card0_issue'); 75 | $card1_issue = $this->gen("issue_card", $data[1],'card1_issue'); 76 | $card0_issue_sem = $this_sem['card0_issue']; 77 | $card1_issue_sem = $this_sem['card1_issue']; 78 | return array('text' => 79 | "Currently, you got the " . $card0 . " and the " . $card1 . ". " . 80 | "The " . $card0 . " implies " . $card0_issue . 81 | " This " . $this->gen("nexus", $data) . " the " . $card1 . " which " . 82 | ($card0_issue_sem['omen'] == $card1_issue_sem['omen']? "also ":"") . 83 | "implies " . $card1_issue . " ", 84 | 'sem' => array('type' => 'issue')); 85 | } 86 | 87 | function issue_card($card){ 88 | $omen = "omen_".$this->onto->find_by_path(array($card,'omen')); 89 | $omen_str = $this->lex->string_for_id($omen); 90 | if($this->lex->has("issue_".$card)){ 91 | $issue = "custom"; 92 | $issue_str = " " . $this->lex->string_for_id("issue_".$card) . "."; 93 | }else{ 94 | # general from suit 95 | $suit = $this->onto->find_by_path(array($card,'suit')); 96 | if($suit){ 97 | $issue = "suit"; 98 | if(isset($this->context['issue_suit']) && $this->context['issue_suit'] == $suit){ 99 | $issue_str=" Same as the other card, this is also a ". $suit . "."; # do not repeat 100 | }else{ 101 | $issue_str = " " . ucfirst($this->gen("describe_suit", $suit)) . "."; 102 | $this->context['issue_suit'] = $suit; 103 | } 104 | }else{ 105 | $issue = "unknown"; 106 | $issue_str = ""; 107 | } 108 | } 109 | return array('text' => $omen_str . "." . $issue_str, 110 | 'sem'=>array('omen'=>$omen, 'issue'=>$issue)); 111 | } 112 | 113 | function nexus($data){ 114 | if($this->onto->find_by_path(array($data[0],"opposes",$data[1]))){ 115 | return array('text'=>"strongly opposes", 'sem'=>array('type'=>'connective', 'kind'=>'contrast')); 116 | }elseif ($this->onto->find_by_path(array($data[0],"reinforces",$data[1]))){ 117 | return array('text'=>"strongly reinforces", 'sem'=>array('type'=>'connective', 'kind'=>'list')); 118 | }else{ 119 | $omen0 = $this->onto->find_by_path(array($data[0],"omen")); 120 | $omen1 = $this->onto->find_by_path(array($data[1],"omen")); 121 | if($omen0 == "neutral" || $omen1 == "neutral") { 122 | return array('text'=>"follows", 'sem'=>array('type'=>'connective', 'kind'=>'joint'));; 123 | }elseif ($omen0 == $omen1) { 124 | return array('text'=>"reinforces", 'sem'=>array('type'=>'connective', 'kind'=>'list')); 125 | }else{ 126 | return array('text'=>"opposes", 'sem'=>array('type'=>'connective', 'kind'=>'list')); 127 | } 128 | } 129 | } 130 | 131 | function time($data){ 132 | return 133 | "While the " . 134 | $this->gen("card", $data[0],'card0') . " is leaving, it " . 135 | $this->gen("nexus", $data) . " the " . 136 | $this->gen("card", $data[1],'card1') . " that is arriving."; 137 | } 138 | 139 | function consciousness($data){ 140 | return 141 | "Deep under, you feel like the " . 142 | $this->gen("card", $data[0], 'card0') . ". It " . 143 | $this->gen("nexus", $data) . " the " . 144 | $this->gen("card", $data[1], 'card1') . 145 | " that is what you feel consciously."; 146 | } 147 | 148 | function perception($data){ 149 | return 150 | "You see yourself " . 151 | $this->gen("card", $data[0],'card0') . ". Then it " . 152 | $this->gen("nexus", $data) . " " . 153 | $this->gen("card", $data[1], 'card1') . 154 | " that is how the others see you."; 155 | } 156 | 157 | function hope_fears($data){ 158 | return 159 | "For your hope and fears, we see the " . 160 | $this->gen("card", $data[0]) . "."; 161 | } 162 | 163 | function outcome($data){ 164 | return 165 | "Finally, the outcome is the " . 166 | $this->gen("card", $data[0]) . "."; 167 | } 168 | 169 | function card($card){ 170 | if($this->lex->has($card)){ 171 | return $this->lex->string_for_id($card); 172 | } 173 | # build card from ontology 174 | $onto_frame=$this->onto->find($card); 175 | if(isset($onto_frame['name'])){ 176 | return $onto_frame['name']; 177 | }else{ 178 | return $this->gen("card_number",$onto_frame['number'])." of ".$onto_frame['suit']; 179 | } 180 | } 181 | 182 | function card_number($num){ 183 | $card_num = "card_number_".$num; 184 | if($this->lex->has($card_num)){ 185 | return $this->lex->string_for_id($card_num); 186 | } 187 | return $this->lex->number_to_string($num); 188 | } 189 | 190 | function describe_suit($suit){ 191 | $suit_descr="suit_description_".$suit; 192 | if($this->lex->has($suit_descr)){ 193 | return "the ".$suit." are " .$this->lex->string_for_id($suit_descr); 194 | } 195 | return "the ".$suit. " are ".$suit; 196 | } 197 | 198 | function omen($card){ 199 | return omen2num($this->onto->find_by_path(array($card,'omen'))); 200 | } 201 | 202 | } 203 | 204 | 205 | function omen2num($omen){ 206 | if($omen == "good"){ 207 | return 1; 208 | }elseif($omen =="bad"){ 209 | return -1; 210 | } 211 | return 0; 212 | } 213 | -------------------------------------------------------------------------------- /examples/tarot/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nlgen/nlgen-example-tarot", 3 | "description": "Tarot generator example", 4 | "type": "project", 5 | "keywords": [], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Pablo Duboue", 10 | "email": "pablo.duboue@gmail.com", 11 | "homepage": "http://duboue.net", 12 | "role": "Developer" 13 | } 14 | ], 15 | "repositories" : [ 16 | { 17 | "type":"package", 18 | "package" : { 19 | "name":"nlgen/nlgen", 20 | "version":"0.1", 21 | "dist":{ 22 | "url":"../local_install/nlgen.zip", 23 | "type":"zip" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "NLGen\\": "src/" 28 | } 29 | } 30 | } 31 | } 32 | ], 33 | "require": { 34 | "php": ">=7.2", 35 | "nlgen/nlgen":"*" 36 | }, 37 | "autoload": { 38 | "classmap": [ "." ] 39 | }, 40 | "scripts": { 41 | "archive-package": [ 42 | "mkdir -p ../local_install", 43 | "@composer archive --working-dir=../.. --dir=examples/local_install --file=nlgen --format=zip" 44 | ], 45 | "pre-install-cmd": "@archive-package", 46 | "pre-update-cmd": "@archive-package" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/tarot/lexicon.json: -------------------------------------------------------------------------------- 1 | { 2 | "card_number_1":{ "POS" : "noun", "string" : "ace" }, 3 | "card_number_11":{ "POS" : "noun", "string" : "page" }, 4 | "card_number_12":{ "POS" : "noun", "string" : "knight" }, 5 | "card_number_13":{ "POS" : "noun", "string" : "queen" }, 6 | "card_number_14":{ "POS" : "noun", "string" : "king" } , 7 | "opening_very_positive":[ 8 | { "POS" : "full_phrase", "string" : "Wow, look at those cards!" }, 9 | { "POS" : "full_phrase", "string" : "Lucky today, aren't we?" }, 10 | { "POS" : "full_phrase", "string" : "Why I never get such good cards myself, I wonder." }, 11 | { "POS" : "full_phrase", "string" : "Sometimes life is good to people, that makes me happy." }, 12 | { "POS" : "full_phrase", "string" : "Take a photo of these cards and keep them, they shine!" } ], 13 | "opening_positive":[ 14 | { "POS" : "full_phrase", "string" : "Overall, cards look good, let's get down to business." }, 15 | { "POS" : "full_phrase", "string" : "Good, good." }, 16 | { "POS" : "full_phrase", "string" : "Not bad, not bad at all." }, 17 | { "POS" : "full_phrase", "string" : "Luck can be helped but overall is looking good." }, 18 | { "POS" : "full_phrase", "string" : "Things seem to be doing well, I would say." } ], 19 | "opening_neutral":[ 20 | { "POS" : "full_phrase", "string" : "Life is you make out of it, same with these cards." }, 21 | { "POS" : "full_phrase", "string" : "Not bad, not good, just your cards." }, 22 | { "POS" : "full_phrase", "string" : "And what is this supposed to mean?" }, 23 | { "POS" : "full_phrase", "string" : "Hmmm." }, 24 | { "POS" : "full_phrase", "string" : "Let us see how things play out here." } ], 25 | "opening_negative":[ 26 | { "POS" : "full_phrase", "string" : "Oh, well, you'll need to work a little bit or so it seems." }, 27 | { "POS" : "full_phrase", "string" : "Not easy times ahead, I should say." }, 28 | { "POS" : "full_phrase", "string" : "And what is this supposed to mean?" }, 29 | { "POS" : "full_phrase", "string" : "Ouch." }, 30 | { "POS" : "full_phrase", "string" : "We will have to go slow." } ], 31 | "opening_very_negative":[ 32 | { "POS" : "full_phrase", "string" : "Agh, this will take a lot of work." }, 33 | { "POS" : "full_phrase", "string" : "You are not going to like this." }, 34 | { "POS" : "full_phrase", "string" : "Don't shoot the messenger, please." }, 35 | { "POS" : "full_phrase", "string" : "Maybe we should just skip this." }, 36 | { "POS" : "full_phrase", "string" : "These cards are unusually bad." } ], 37 | "omen_good" : [ { "POS" : "phrase", "string" : "good things to come" }, { "POS" : "phrase", "string" : "a positive outlook" } ], 38 | "omen_neutral" : [ { "POS" : "phrase", "string" : "a little bit of a puzzle" }, { "POS" : "phrase", "string" : "a mixed outlook" } ], 39 | "omen_bad" : { "POS" : "phrase", "string" : "something bad" }, 40 | "issue_the_lovers" : {"POS" : "phrase", "string" : "The lovers refers to relationships, sexuality but also personal beliefs and values" }, 41 | "suit_description_wands" : { "POS" : "phrase", "string" : "adventurous, energetic" }, 42 | "suit_description_pentacles" : { "POS" : "phrase", "string" : "nurturing, concrete" }, 43 | "suit_description_swords" : { "POS" : "phrase", "string" : "intellectual, analytical" }, 44 | "suit_description_cups" : { "POS" : "phrase", "string" : "loving, compassionate" } 45 | } -------------------------------------------------------------------------------- /examples/tarot/ontology.json: -------------------------------------------------------------------------------- 1 | { 2 | "the_fool": 3 | { "class": "card", 4 | "name": "fool", 5 | "opposes" : { "death": 1 }, 6 | "omen" : "good" 7 | }, 8 | "death": 9 | { "class": "card", 10 | "name": "death", 11 | "opposes" : { "the_fool": 1 }, 12 | "omen" : "bad" 13 | }, 14 | "the_tower": 15 | { "class": "card", 16 | "name": "tower", 17 | "opposes" : { "the_lovers": 1 }, 18 | "omen" : "bad" 19 | }, 20 | "the_lovers": 21 | { "class": "card", 22 | "name": "lovers", 23 | "opposes" : { "the_tower": 1 }, 24 | "omen": "good" 25 | }, 26 | "the_magician": 27 | { "class": "card", 28 | "name": "magician", 29 | "omen" : "good" 30 | }, 31 | "the_empress": 32 | { "class": "card", 33 | "name": "empress", 34 | "omen" : "neutral" 35 | }, 36 | "pentacles_1": 37 | { "class": "card", 38 | "suit": "pentacles", 39 | "number": "1", 40 | "omen" : "good" 41 | }, 42 | "pentacles_2": 43 | { "class": "card", 44 | "suit": "pentacles", 45 | "number": "2", 46 | "omen" : "neutral" 47 | }, 48 | "pentacles_3": 49 | { "class": "card", 50 | "suit": "pentacles", 51 | "number": "3", 52 | "omen" : "neutral" 53 | }, 54 | "pentacles_4": 55 | { "class": "card", 56 | "suit": "pentacles", 57 | "number": "4", 58 | "omen" : "good" 59 | }, 60 | "pentacles_5": 61 | { "class": "card", 62 | "suit": "pentacles", 63 | "number": "5", 64 | "omen" : "neutral" 65 | } 66 | } -------------------------------------------------------------------------------- /examples/tarot/tarot.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | require __DIR__ . '/vendor/autoload.php'; 27 | 28 | $ontology = file_get_contents("ontology.json"); 29 | $lexicon = file_get_contents("lexicon.json"); 30 | 31 | $gen = new TarotGenerator($ontology, $lexicon); 32 | 33 | # get all cards 34 | $cards = $gen->onto->find_all_by_class('card'); 35 | shuffle($cards); 36 | 37 | print "CARDS:\n"; 38 | print_r($cards); 39 | 40 | print $gen->generate(array_slice($cards,0,10))."\n"; 41 | 42 | //print_r($gen->semantics()); 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/webnlg/.gitignore: -------------------------------------------------------------------------------- 1 | memory.jsons 2 | -------------------------------------------------------------------------------- /examples/webnlg/README.md: -------------------------------------------------------------------------------- 1 | # WebNLG Challenge Driver 2 | 3 | A not particularly very succesfull attempt at the WebNLG challenge. 4 | 5 | The memory.jsons file was obtained by parsing the provided data and it 6 | is thus distributed under the same license: 7 | 8 | CC Attribution-Noncommercial-Share Alike 4.0 International. 9 | 10 | As the file is 15Mb in size, it is stored elsewhere: http://duboue.net/download/memory.jsons 11 | 12 | To execute the driver you will also need the delex_dict.json from 13 | their baseline system. 14 | 15 | `php webnlg_driver.php memory.jsons benchmark.xml delex_dict.json` 16 | 17 | will show the verbalizations to stdout 18 | 19 | `php webnlg_driver.php memory.jsons benchmark.xml delex_dict.json folder/to/store/reference` 20 | 21 | will show the verbalizations to stdout and write reference files in 22 | the same format needed by the BLEU evaluation scripts. 23 | 24 | To generate a full evaluation: 25 | 26 | ```bash 27 | rm /path/to/reference/*.lex 28 | rm /path/to/reference/relexicalised_predictions.txt 29 | for xml in /path/to/dev/data/*/*.xml 30 | do 31 | php webnlg_driver.php memory.jsons benchmark.xml delex_dict.json /path/to/references >> /path/to/reference/relexicalised_predictions.txt 32 | done 33 | ``` 34 | -------------------------------------------------------------------------------- /examples/webnlg/webnlg_driver.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | 27 | $header = ""; 28 | 29 | use nlgen\Generator; 30 | 31 | global $argv,$argc; 32 | 33 | // usage: webnlg_driver.php memory.json eval.xml delex_dict.json ( write eval to this folder, if defined ) 34 | 35 | if($argc>1){ 36 | require '../ste/ste.php'; 37 | } 38 | 39 | // load the memory 40 | 41 | $memory_str = file($argv[1]); 42 | $memory = array(); 43 | 44 | foreach($memory_str as $memory_line){ 45 | $memory[] = json_decode($memory_line, TRUE); 46 | } 47 | 48 | //echo "********* MEMORY LOADED\n"; 49 | 50 | // load the xml 51 | $bench_str = file_get_contents($argv[2]); 52 | $xmlstr = $header.$bench_str; 53 | $benchmark = new SimpleXMLElement($xmlstr); 54 | 55 | //echo "********* BENCHMARK LOADED\n"; 56 | 57 | // load the type data 58 | // (this should be moved to a redis call in a production system) 59 | $types = json_decode(file_get_contents($argv[3]), TRUE); 60 | $types_lookup = array(); 61 | foreach($types as $type => $entries){ 62 | foreach($entries as $entry){ 63 | $types_lookup[$entry] = $type; 64 | } 65 | } 66 | 67 | //echo "********* TYPE DATA LOADED\n"; 68 | 69 | $lexfolder = FALSE; 70 | if($argv>4){ 71 | $lexfolder = $argv[4]; 72 | $lexfps = array(); 73 | for($i=0; $i<8; $i++){ 74 | $lexfps[] = fopen("$lexfolder/all-notdelex-reference".strval($i).".lex", "a"); 75 | } 76 | } 77 | 78 | // go through the benchmark 79 | foreach($benchmark->entries->entry as $entry){ 80 | 81 | //print_r($entry); 82 | 83 | // build the triples in their format 84 | $input = array(); 85 | $eid = $entry['eid']; 86 | $tripleset = $entry->modifiedtripleset; 87 | $triples = array(); 88 | foreach($tripleset->mtriple as $mtriple){ 89 | $triple = explode(' | ', ((string)$mtriple)); 90 | $s=array($triple[0], $triple[0]); 91 | $p=array($triple[1], $triple[1]); 92 | $o=array($triple[2], $triple[2]); 93 | if(array_key_exists($s[0], $types_lookup)){ 94 | $s[1] = implode(' ', 95 | preg_split('/(\W)/', 96 | preg_replace('/"/', "'", 97 | preg_replace('/_/', ' ', 98 | strtoupper($types_lookup[$s[0]]))))); 99 | } 100 | $p[1] = implode(' ', 101 | preg_split('/(\W)/', 102 | preg_replace('/"/', "'", 103 | preg_replace('/_/', ' ', $p[0])))); 104 | $o[1] = implode(' ', 105 | preg_split('/(\W)/', 106 | preg_replace('/"/', "'", 107 | preg_replace('/_/', ' ', 108 | strtoupper($p[0]))))); 109 | $mytriple = array($s, $p, $o); 110 | for($i=0; $i<3; $i++){ 111 | $mytriple[$i][] = implode(' ', 112 | preg_split('/(\W)/', 113 | preg_replace('/"/', "'", 114 | preg_replace('/_/', ' ', $mytriple[$i][0])))); 115 | } 116 | $triples[] = $mytriple; 117 | //print_r($triple); 118 | //print_r($mytriple); 119 | } 120 | 121 | $remaining = $triples; 122 | $loops = 0; 123 | $generated = ""; 124 | while(count($remaining) > 0){ 125 | 126 | // search for relevant sentences from the memory 127 | $selected = false; 128 | $matched = 0; 129 | foreach($memory as $m){ 130 | $triples_found = array(); 131 | //TODO: use context, too 132 | foreach($m['triples'] as $triple){ 133 | foreach($remaining as $source){ 134 | $all_good = TRUE; 135 | for($i=0; $i<3; $i++){ 136 | if(!($triple[$i] == $source[$i][1] || 137 | $triple[$i] == $source[$i][2])){ 138 | $all_good = FALSE; 139 | } 140 | } 141 | if($all_good){ 142 | $triples_found[] = array($triple, $source); 143 | break; 144 | } 145 | } 146 | if(count($triples_found) > 0 && 147 | (!$selected || count($matched) < count($triples_found))){ 148 | $selected = $m; 149 | $matched = $triples_found; 150 | } 151 | } 152 | } 153 | 154 | if(!$selected){ 155 | // it's the end of the world as we knew it 156 | foreach($remaining as $triple){ 157 | $generated .= $triple[0][2] . " " . $triple[1][2] . " " . $triple[2][2] . " "; 158 | } 159 | $remaining = array(); 160 | }else{ 161 | // plug-in the data and re-generate 162 | $sent = $gen->generate($selected['tree']); 163 | foreach($triples as $triple){ 164 | $sent = str_replace($triple[0][1], $triple[0][2], $sent); 165 | $sent = str_replace($triple[2][1], $triple[2][2], $sent); 166 | } 167 | $generated .= $sent . " "; 168 | $new_remaining = array(); 169 | foreach($remaining as $triple){ 170 | $was_consumed = FALSE; 171 | foreach($matched as $consumed){ 172 | if($triple == $consumed[1]){ 173 | $was_consumed = TRUE; 174 | break; 175 | } 176 | } 177 | if(!$was_consumed){ 178 | $new_remaining[] = $triple; 179 | } 180 | } 181 | if(count($remaining) == count($new_remaining)){ 182 | echo "ENTRY:\n"; 183 | print_r($entry); 184 | echo "REMAINING:\n"; 185 | print_r($remaining); 186 | echo "MATCHED:\n"; 187 | print_r($matched); 188 | die("Shouldn't happend"); 189 | } 190 | $remaining = $new_remaining; 191 | } 192 | } 193 | 194 | // print output on their format 195 | echo trim(strtolower(implode(' ', preg_split('/(\W)/', $generated))))."\n"; 196 | 197 | if($lexfolder){ 198 | $lex = array(); 199 | foreach($entry->lex as $l){ 200 | $lex[] = strtolower(implode(' ', preg_split('/(\W)/', (string)$l)))."\n"; 201 | } 202 | for($i=0; $i<8; $i++){ 203 | if($i>=count($lex)){ 204 | fprintf($lexfps[$i], "\n"); 205 | }else{ 206 | fprintf($lexfps[$i], $lex[$i]); 207 | } 208 | } 209 | } 210 | } 211 | if($lexfolder){ 212 | for($i=0; $i<8; $i++){ 213 | fclose($lexfps[$i]); 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src 6 | 7 | 8 | 9 | 10 | tests 11 | 12 | 13 | tests/Availability 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /scripts/compileGrammars.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | $loader = require __DIR__ . '/../vendor/autoload.php'; 27 | 28 | use NLGen\Grammars\Availability\AvailabilityGrammar; 29 | 30 | $class = AvailabilityGrammar::class; 31 | $path = realpath($loader->findFile($class)); 32 | $lexicon = file_get_contents(dirname($path)."/lexicon_en.json"); 33 | 34 | file_put_contents(dirname($path)."/AvailabilityGenerator.php", 35 | AvailabilityGrammar::CompileWithName("AvailabilityGenerator","NLGen\\Grammars\\Availability", 36 | null,null,false,null,$lexicon)); 37 | 38 | -------------------------------------------------------------------------------- /src/Grammars/Availability/AvailabilityGenerator.php: -------------------------------------------------------------------------------- 1 | context['debug'])) { 53 | error_log(print_r(func_get_args(),true)); 54 | } 55 | return $this->gen("focusedMessage_orig", func_get_args(), "focusedMessage"); 56 | } 57 | 58 | function blocks_orig($params){ 59 | return AvailabilityGrammar::blocks($params[0],$params[1],$params[2]); 60 | } 61 | 62 | function blocks($p0,$p1,$p2){ 63 | if(isset($this->context['debug'])) { 64 | error_log(print_r(func_get_args(),true)); 65 | } 66 | return $this->gen("blocks_orig", func_get_args(), "blocks"); 67 | } 68 | 69 | function purity_orig($params){ 70 | return AvailabilityGrammar::purity($params[0]); 71 | } 72 | 73 | function purity($p0){ 74 | if(isset($this->context['debug'])) { 75 | error_log(print_r(func_get_args(),true)); 76 | } 77 | return $this->gen("purity_orig", func_get_args(), "purity"); 78 | } 79 | 80 | function block_orig($params){ 81 | return AvailabilityGrammar::block($params[0],$params[1],$params[2]); 82 | } 83 | 84 | function block($p0,$p1,$p2){ 85 | if(isset($this->context['debug'])) { 86 | error_log(print_r(func_get_args(),true)); 87 | } 88 | return $this->gen("block_orig", func_get_args(), "block"); 89 | } 90 | 91 | function dows_orig($params){ 92 | return AvailabilityGrammar::dows($params[0]); 93 | } 94 | 95 | function dows($p0){ 96 | if(isset($this->context['debug'])) { 97 | error_log(print_r(func_get_args(),true)); 98 | } 99 | return $this->gen("dows_orig", func_get_args(), "dows"); 100 | } 101 | 102 | function timeRange_orig($params){ 103 | return AvailabilityGrammar::timeRange($params[0],$params[1]); 104 | } 105 | 106 | function timeRange($p0,$p1){ 107 | if(isset($this->context['debug'])) { 108 | error_log(print_r(func_get_args(),true)); 109 | } 110 | return $this->gen("timeRange_orig", func_get_args(), "timeRange"); 111 | } 112 | 113 | function hour_orig($params){ 114 | return AvailabilityGrammar::hour($params[0]); 115 | } 116 | 117 | function hour($p0){ 118 | if(isset($this->context['debug'])) { 119 | error_log(print_r(func_get_args(),true)); 120 | } 121 | return $this->gen("hour_orig", func_get_args(), "hour"); 122 | } 123 | 124 | protected function is_sealed() { return TRUE; } 125 | } 126 | -------------------------------------------------------------------------------- /src/Grammars/Availability/FocusedSegmentMessage.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | namespace NLGen\Grammars\Availability; 27 | 28 | class FocusedSegmentMessage { 29 | 30 | // focus constants 31 | public const DAYS = 0; 32 | public const SEGMENT = 1; 33 | public const WEEK = 2; 34 | 35 | public int $focus; // one of the constants above 36 | public array $startTime; // array int (hour), int (minute) 37 | public array $endTime; 38 | public array $dows; // array int (day-of-the-week) 39 | public bool $fullRange; // do the start and end cover the full range of the day? 40 | public array $blocks; // fine grain details 41 | 42 | public function __construct(int $focus, array $startTime, array $endTime, array $dows, bool $fullRange, array $blocks) { 43 | $this->focus = $focus; 44 | $this->startTime = $startTime; 45 | $this->endTime = $endTime; 46 | $this->dows = $dows; 47 | $this->fullRange = $fullRange; 48 | $this->blocks = $blocks; 49 | } 50 | 51 | public function semantics(): array { 52 | $block_sem = []; 53 | foreach($this->blocks as $block) { 54 | $block_sem[] = $block->semantics(); 55 | } 56 | return [ 'startTime' => $this->startTime, 57 | 'endTime' => $this->endTime, 58 | 'dows' => $this->dows, 59 | 'fullRange' => $this->fullRange, 60 | 'blocks' => $block_sem, 61 | ]; 62 | } 63 | 64 | public function minutes() : int { 65 | $result = 0; 66 | foreach($this->blocks as $block) { 67 | $result += $block->minutes(); 68 | } 69 | return $result; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Grammars/Availability/MeetingBlockMessage.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | namespace NLGen\Grammars\Availability; 27 | 28 | require_once __DIR__ . "/util.php"; 29 | 30 | class MeetingBlockMessage { 31 | 32 | public array $startTime; // array int (hour), int (minute) 33 | public array $endTime; 34 | public array $dows; // array int (day-of-the-week) 35 | public bool $isFree; // true if this blocks means free time 36 | public float $purity; // percentage of the block that is completely free or not free (depending on $isFree) 37 | public bool $fullRange; // do the start and end cover the full range of the day? 38 | 39 | public function __construct(array $startTime, array $endTime, array $dows, bool $isFree, float $purity, bool $fullRange) { 40 | $this->startTime = $startTime; 41 | $this->endTime = $endTime; 42 | $this->dows = $dows; 43 | $this->isFree = $isFree; 44 | $this->purity = $purity; 45 | $this->fullRange = $fullRange; 46 | } 47 | 48 | public function includesOther(MeetingBlockMessage $other) : bool { 49 | return ($this->startTime[0] < $other->startTime[0] || ( 50 | $this->startTime[0] == $other->startTime[0] && 51 | $this->startTime[1] <= $other->startTime[1])) && 52 | ($this->endTime[0] > $other->endTime[0] || 53 | ($this->endTime[0] == $other->endTime[0] && 54 | $this->endTime[1] >= $other->endTime[1])); 55 | } 56 | 57 | public function includes(array $start, array $end) : bool { 58 | return ($this->startTime[0] < $start[0] || ( 59 | $this->startTime[0] == $start[0] && 60 | $this->startTime[1] <= $start[1])) && 61 | ($this->endTime[0] > $end[0] || 62 | ($this->endTime[0] == $end[0] && 63 | $this->endTime[1] >= $end[1])); 64 | } 65 | 66 | public function splitOther(MeetingBlockMessage $other) : array { 67 | $result = []; 68 | 69 | if($this->startTime == $other->startTime) { 70 | if($this->endTime == $other->endTime) { 71 | return [ $other ]; 72 | } 73 | }else{ 74 | $result[] = new MeetingBlockMessage($this->startTime, $other->startTime, $this->dows, $this->isFree, $this->purity, false); 75 | } 76 | $result[] = $other; 77 | if($this->endTime != $other->endTime) { 78 | $result[] = new MeetingBlockMessage($other->endTime, $this->endTime, $this->dows, $this->isFree, $this->purity, false); 79 | } 80 | 81 | return $result; 82 | } 83 | 84 | public function split(array $start, array $end, bool $isFree) : array { 85 | return $this->splitOther(new MeetingBlockMessage($start, $end, $this->dows, $isFree, $this->purity, false)); 86 | } 87 | 88 | public function semantics(): array { 89 | return [ 'startTime' => $this->startTime, 90 | 'endTime' => $this->endTime, 91 | 'dows' => $this->dows, 92 | 'isFree' => $this->isFree, 93 | 'purity' => $this->purity, 94 | 'fullRange' => $this->fullRange 95 | ]; 96 | } 97 | 98 | public function minutes() : int { 99 | return minDiff($this->startTime, $this->endTime) * count($this->dows); 100 | } 101 | 102 | public function __toString() : string { 103 | $dowS =[]; 104 | foreach($this->dows as $dow){ 105 | $dowS[] = strval($dow); 106 | } 107 | 108 | return ($this->isFree?"FREE([":"BUSY([").implode(",",$dowS)."]=".sprintf("%d:%02d",$this->startTime[0],$this->startTime[1])."-". 109 | sprintf("%d:%02d", $this->endTime[0],$this->endTime[1])." ".sprintf("%3d%%",$this->purity*100). 110 | ($this->fullRange?" fullrange":"").")"; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Grammars/Availability/lexicon_en.json: -------------------------------------------------------------------------------- 1 | { 2 | "all_week":"all week" 3 | ,"all_day":"all day" 4 | ,"dow0":"Monday" 5 | ,"dow1":"Tuesday" 6 | ,"dow2":"Wednesday" 7 | ,"dow3":"Thursday" 8 | ,"dow4":"Friday" 9 | ,"dow5":"Saturday" 10 | ,"dow6":"Sunday" 11 | ,"and":"and" 12 | ,"morning":"in the morning" 13 | ,"afternoon":"in the afternoon" 14 | ,"mornings":"the mornings" 15 | ,"afternoons":"the afternoons" 16 | ,"mid_morning":"in the mid-morning" 17 | ,"mid_afternoon":"in the mid-afternoon" 18 | ,"early_morning":"in the early morning" 19 | ,"late_afternoon":"in the late afternoon" 20 | ,"from":"from" 21 | ,"to":"to" 22 | ,"around":"around" 23 | ,"late":"late" 24 | ,"half_past":"half past" 25 | ,"be":[ { "string":"is", "number":"sg" } 26 | ,{ "string":"are","number":"pl" } ] 27 | ,"mostly":[ { "string": "mostly", "likelihood": 2.0}, {"string":"quite"}] 28 | ,"somewhat":"somewhat" 29 | ,"almost":"almost" 30 | ,"also":"also" 31 | ,"free_choice":[{ "string": "free"} , {"string": "available" }] 32 | ,"busy_choice":[{ "string": "busy"}, {"string": "unavailable" }, {"string":"taken"}, {"string":"committed"} ] 33 | ,"free":{ "string": "free" } 34 | ,"busy":{ "string": "busy" } 35 | ,"rest_free":"the rest is free" 36 | ,"rest_busy":"the rest is busy" 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/Grammars/Availability/util.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | namespace NLGen\Grammars\Availability; 27 | 28 | // diff in minutes between two times expressed as integer tuples hour, minute 29 | function minDiff($a, $b) : int { 30 | $start = $a; 31 | $end = $b; 32 | if($a[0] > $b[0] || ($a[0] == $b[0] && $a[1] > $b[1])) { 33 | $start = $b; 34 | $end = $a; 35 | } 36 | return ($end[0] - $start[0]) * 60 - $start[1] + $end[1]; 37 | } 38 | 39 | function intersection(array $a, array $b) : int { 40 | $result = 0; 41 | foreach($a as $t => $x) { 42 | if(isset($b[$t])) { 43 | $result++; 44 | } 45 | } 46 | return $result; 47 | } 48 | 49 | // print a time table of day-of-week into array of entries 50 | function tableToString(array $table) : string { 51 | $result = ""; 52 | foreach($table as $dow => $entries) { 53 | if($result) { 54 | $result .= "\n"; 55 | } 56 | $es = []; 57 | foreach($entries as $entry){ 58 | $es[] = strval($entry); 59 | } 60 | $result.="$dow: ".implode("; ", $es); 61 | } 62 | return $result; 63 | } 64 | 65 | function containsTime(array $start, array $end, array $time): bool { 66 | return (($start[0] < $time[0] or ($start[0] == $time[0] and $start[1] <= $time[1])) and 67 | ($time[0] < $end[0] or ($end[0] == $time[0] and $time[1] <= $end[1]))); 68 | } 69 | 70 | function overlaps(array $start1, array $end1, array $start2, array $end2): bool { 71 | return containsTime($start1, $end1, $start2) or 72 | containsTime($start1, $end1, $end2) or 73 | containsTime($start2, $end2, $start1) or 74 | containsTime($start2, $end2, $end1); 75 | } 76 | 77 | function maxTime(array $time1, array $time2) { 78 | if($time1[0] < $time2[0]){ 79 | return $time2; 80 | } 81 | if($time1[0] > $time2[0]){ 82 | return $time1; 83 | } 84 | if($time1[1] < $time2[1]){ 85 | return $time2; 86 | } 87 | if($time1[1] > $time2[1]){ 88 | return $time1; 89 | } 90 | return $time1; 91 | } 92 | 93 | function minTime(array $time1, array $time2) { 94 | if($time1[0] > $time2[0]){ 95 | return $time2; 96 | } 97 | if($time1[0] < $time2[0]){ 98 | return $time1; 99 | } 100 | if($time1[1] > $time2[1]){ 101 | return $time2; 102 | } 103 | if($time1[1] < $time2[1]){ 104 | return $time1; 105 | } 106 | return $time1; 107 | } 108 | 109 | -------------------------------------------------------------------------------- /src/Lexicon.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | namespace NLGen; 27 | 28 | class Lexicon 29 | { 30 | var $id_to_entries = array(); 31 | var $generator; 32 | 33 | public function __construct($generator, $json_text) 34 | { 35 | $this->generator = $generator; 36 | 37 | if(! $json_text){ 38 | return; 39 | } 40 | $array = json_decode($json_text,TRUE); 41 | 42 | if(! $array){ 43 | print "$json_text"; 44 | 45 | // Define the errors. 46 | $constants = get_defined_constants(true); 47 | $json_errors = array(); 48 | foreach ($constants["json"] as $name => $value) { 49 | if (!strncmp($name, "JSON_ERROR_", 11)) { 50 | $json_errors[$value] = $name; 51 | } 52 | } 53 | die ($json_errors[json_last_error()]); 54 | } 55 | 56 | foreach ($array as $id => $value) { 57 | if(is_string($value)) { 58 | $value = [ 'string' => $value ]; 59 | } 60 | if(Lexicon::has_multiple($value)){ 61 | # copy non-array entries from $frame 62 | $new_value = array(); 63 | $to_copy = array(); 64 | $to_copy['id'] = $id; 65 | $has_key = false; 66 | foreach($value as $v){ 67 | if(is_array($v)){ 68 | $new_value[]=$v; 69 | }else{ 70 | if($has_key){ 71 | $to_copy[$key]=$v; 72 | $has_key=false; 73 | }else{ 74 | $has_key=true; 75 | $key = $v; 76 | } 77 | } 78 | } 79 | foreach($new_value as &$nv){ 80 | foreach($to_copy as $key => $v){ 81 | if(!isset($nv[$key])){ 82 | $nv[$key] = $v; 83 | } 84 | } 85 | } 86 | $value = $new_value; 87 | }else{ 88 | $value['id'] = $id; 89 | } 90 | $this->id_to_entries[$id] = $value; 91 | } 92 | } 93 | 94 | # these functions might need to be overriden 95 | 96 | # return one, at random 97 | public function find($id) 98 | { 99 | if(isset($this->id_to_entries[$id])){ 100 | $frame = $this->id_to_entries[$id]; 101 | if(Lexicon::has_multiple($frame)){ 102 | return Lexicon::get_random($frame); 103 | }else{ 104 | return $frame; 105 | } 106 | } 107 | return NULL; 108 | } 109 | 110 | public function has($id) 111 | { 112 | return isset($this->id_to_entries[$id]); 113 | } 114 | 115 | # return all 116 | public function find_all($id) 117 | { 118 | return isset($this->id_to_entries[$id])?$this->id_to_entries[$id]:NULL; 119 | } 120 | 121 | # complex query, all the keys in the query have to be defined in the entries. 122 | # if a value is itself an array, it is understood as an OR of the values 123 | public function query($query) 124 | { 125 | # compile query 126 | $compiled_query = array(); 127 | foreach($query as $key => $value) { 128 | $compiled=array(); 129 | if(is_array($value)){ 130 | foreach($value as $v){ 131 | $compiled[$v] = true; 132 | } 133 | }else{ 134 | $compiled[$value] = true; 135 | } 136 | $compiled_query[$key] = $compiled; 137 | } 138 | # go through the whole lexicon 139 | $result = array(); 140 | foreach($this->id_to_entries as $id => $allentries) { 141 | $entries = is_array($allentries) ? $allentries : [ $allentries ]; 142 | if(isset($entries['id'])){ 143 | $entries = [ $entries ]; 144 | } 145 | #print "entries:"; print_r($entries); 146 | foreach($entries as $entry) { 147 | if(!is_array($entry)){ 148 | continue; 149 | } 150 | $good = true; 151 | foreach($compiled_query as $key => $goodvalues) { 152 | $set = isset($entry[$key]); 153 | if((!$set || !isset($goodvalues[$entry[$key]]))&& 154 | ($set || !isset($goodvalues["undefined"]))){ 155 | #print("failed:\n"); print_r($entry); 156 | #print("key: "); print_r($key."\n"); 157 | #print("goodvalues:"); print_r($goodvalues); 158 | $good=false; 159 | break; 160 | } 161 | } 162 | if($good){ 163 | $result[] = $entry; 164 | } 165 | } 166 | } 167 | #print "query_result:"; print_r($result); 168 | return $result; 169 | } 170 | 171 | # the rest doesn't need to be overriden in subclasses 172 | public function query_string($query) 173 | { 174 | $frames = $this->query($query); 175 | if($frames) { 176 | if(isset($frames[0]['string'])) { 177 | return $frames[0]['string']; 178 | }else{ 179 | return "NOT FOUND string: '".print_r($query, true)."'"; 180 | } 181 | } 182 | return "NOT FOUND: '".print_r($query, true)."'"; 183 | } 184 | 185 | public function string_for_id($id,$data=array()) 186 | { 187 | $frame = $this->find($id); 188 | if($frame){ 189 | return $this->string($frame, $data); 190 | }else{ 191 | return "NOT FOUND: '$id'"; 192 | } 193 | } 194 | 195 | public function string($frame, $data=array()) 196 | { 197 | if(Lexicon::is_function($frame)){ 198 | $frame = $this->execute_function($frame, $data); 199 | }elseif(Lexicon::is_mixed($frame)){ 200 | $frame = $this->resolve_mixed($frame, $data); 201 | }elseif(Lexicon::is_slot($frame)){ 202 | $frame = $this->resolve_slot($frame, $data); 203 | } 204 | return $frame['string']; 205 | } 206 | 207 | public function resolve($frame, $data=array()) 208 | { 209 | if(Lexicon::is_function($frame)){ 210 | $frame = $this->execute_function($frame, $data); 211 | }elseif(Lexicon::is_mixed($frame)){ 212 | $frame = $this->resolve_mixed($frame, $data); 213 | } 214 | return $frame; 215 | } 216 | 217 | 218 | public static function has_multiple($frame) 219 | { 220 | return isset($frame[0]) && !is_string($frame); 221 | } 222 | 223 | public static function get_random($frame) 224 | { 225 | # use likelihoods 226 | $result = Lexicon::sample($frame); 227 | return $result; 228 | } 229 | 230 | public static function is_function($frame) 231 | { 232 | return isset($frame['function']); 233 | } 234 | 235 | public static function is_slot($frame) 236 | { 237 | return isset($frame['slot']); 238 | } 239 | 240 | public static function is_mixed($frame) 241 | { 242 | return isset($frame['mixed']); 243 | } 244 | 245 | public static function get_mixed($frame) 246 | { 247 | return $frame['mixed']; 248 | } 249 | 250 | public static function get_slot($frame) 251 | { 252 | return $frame['slot']; 253 | } 254 | 255 | public function execute_function($frame, $data) 256 | { 257 | $savepoint = $this->generator->savepoint(); 258 | $result_string = $this->generator->gen($frame['function'], $data,"tmp"); 259 | $len = count($this->generator->semantics); 260 | $result_sem = $this->generator->semantics[$len-1]["tmp"]; 261 | $this->generator->rollback($savepoint); 262 | $result_sem['string']=$result_string; 263 | return $result_sem; 264 | } 265 | 266 | public function resolve_slot($frame, $data){ 267 | $slot = Lexicon::get_slot($frame); 268 | $frame['string'] = $data[$slot]; 269 | return $frame; 270 | } 271 | 272 | public function resolve_mixed($frame, $data) 273 | { 274 | $mixed = Lexicon::get_mixed($frame); 275 | 276 | $result = $frame; 277 | $string = ""; 278 | unset($result['mixed']); 279 | $count = 0; 280 | foreach($mixed as $entry){ 281 | if(is_array($entry)){ 282 | $exec_data = $data; # clone 283 | if(count($entry)>1){ 284 | # extra parameters 285 | foreach($entry as $key=>$value){ 286 | if($key != 'function' && $key != 'mixed' && $key != 'slot'){ 287 | $exec_data[$key] = $value; 288 | } 289 | } 290 | } 291 | if(Lexicon::is_function($entry)){ 292 | $gen = $this->execute_function($entry, $exec_data); 293 | }elseif(Lexicon::is_slot($entry)){ 294 | $gen = $this->resolve_slot($entry, $exec_data); 295 | }elseif(Lexicon::is_mixed($entry)){ 296 | $gen = $this->resolve_mixed($entry, $exec_data); 297 | }else{ 298 | $gen = $entry; 299 | } 300 | $string = $string . $gen['string']; 301 | $result[] = $gen; 302 | }else{ 303 | $string = $string . strval($entry); 304 | } 305 | } 306 | $result['string'] = $string; 307 | return $result; 308 | } 309 | 310 | # take an array with 'likelihood' entries and uniformly sample one 311 | # will add a likelihood entry of 1.0 for entries that don't have it 312 | # ignores non-array entries 313 | public static function sample($frame) 314 | { 315 | #print "frame: "; print_r($frame); 316 | $total = 0; 317 | foreach($frame as &$entry){ 318 | if(!is_array($entry)){ 319 | continue; 320 | } 321 | if(!isset($entry["likelihood"])){ 322 | $entry["likelihood"] = 1.0; 323 | } 324 | $total += floatval($entry["likelihood"]); 325 | } 326 | #print "total: $total\n"; 327 | 328 | $result = NULL; 329 | $rand = rand(0,1000 * $total); 330 | #print "rand: $rand\n"; 331 | $accum = 0; 332 | foreach($frame as &$entry){ 333 | if(!is_array($entry)){ 334 | continue; 335 | } 336 | $accum += floatval($entry["likelihood"]) * 1000.0; 337 | $result = $entry; 338 | if($accum > $rand){ 339 | break; 340 | } 341 | } 342 | #print "result: "; print_r($result); 343 | return $result; 344 | } 345 | 346 | 347 | # The rest is WIP 348 | 349 | public static function get_POS($frame) 350 | { 351 | return $frame['POS']; 352 | } 353 | 354 | public static function pluralize($frame) 355 | { 356 | if(isset($frame['plural'])){ 357 | return $frame['plural']; 358 | } 359 | #TODO add more rules 360 | return $frame['string']."s"; 361 | } 362 | 363 | public static function past($frame, $person) 364 | { 365 | if($person){ 366 | if(isset($frame['past'.$person])) { 367 | return $frame['past'.$person]; 368 | } 369 | } 370 | if(isset($frame['past'])){ 371 | return $frame['past']; 372 | } 373 | #TODO add more rules 374 | return $frame['string']."ed"; 375 | } 376 | 377 | public static function present($frame, $person) 378 | { 379 | if($person){ 380 | if(isset($frame['present'.$person])) { 381 | return $frame['present'.$person]; 382 | } 383 | } 384 | if(isset($frame['present'])){ 385 | $present = $frame['present']; 386 | }else{ 387 | $present = $frame['string']; 388 | } 389 | if($person == "3s"){ 390 | #TODO add more rules 391 | $present = $present . "s"; 392 | } 393 | return $present; 394 | } 395 | 396 | public static function continuous($frame) 397 | { 398 | if(isset($frame['continuous'])){ 399 | return $frame['continuous']; 400 | } 401 | return $frame['string']."ing"; 402 | } 403 | 404 | var $_basic_numbers = array("zero","one","two","three","four","five", 405 | "six","seven","eight","ten","twelve","thirteen","fourteen","fifeteen", 406 | "sixteen","eighteen","nineteen"); 407 | 408 | public function number_to_string($num) 409 | { 410 | if(isset($this->_basic_numbers[$num])){ 411 | return $this->_basic_numbers[$num]; 412 | } 413 | # TODO recurse 414 | return $num; 415 | } 416 | 417 | #TODO pronouns 418 | #TODO complex forms of the verb ('would have been', etc) 419 | #TODO lexical entries that involve calling the generator 420 | } 421 | -------------------------------------------------------------------------------- /src/Ontology.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included 14 | * in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | * DEALINGS IN THE SOFTWARE. 23 | * 24 | */ 25 | 26 | namespace NLGen; 27 | 28 | class Ontology 29 | { 30 | 31 | var $id_to_frames = array(); 32 | 33 | public function __construct($json_text) 34 | { 35 | if(! $json_text){ 36 | return; 37 | } 38 | 39 | $array = json_decode($json_text,true); 40 | 41 | if(! $array){ 42 | print "$json_text"; 43 | 44 | // Define the errors. 45 | $constants = get_defined_constants(true); 46 | $json_errors = array(); 47 | foreach ($constants["json"] as $name => $value) { 48 | if (!strncmp($name, "JSON_ERROR_", 11)) { 49 | $json_errors[$value] = $name; 50 | } 51 | } 52 | die ($json_errors[json_last_error()]); 53 | } 54 | 55 | foreach ($array as $id => $value) { 56 | $value['id'] = $id; 57 | $this->id_to_frames[$id] = $value; 58 | } 59 | } 60 | 61 | # to implement a subclass, override these methods 62 | 63 | public function find($id) 64 | { 65 | return $this->has($id)?$this->id_to_frames[$id]:null; 66 | } 67 | 68 | public function has($id) 69 | { 70 | return isset($this->id_to_frames[$id]); 71 | } 72 | 73 | public function find_all_by_class($class) 74 | { 75 | $result=array(); 76 | foreach($this->id_to_frames as $id => $frame){ 77 | if($frame['class'] == $class){ 78 | $result[] = $id; 79 | } 80 | } 81 | return $result; 82 | } 83 | 84 | # this method doesn't need overriding (but might profit from optimizations) 85 | public function find_by_path($array) 86 | { 87 | $current = $this->find($array[0]); 88 | $path = array_values($array); 89 | unset($path[0]); 90 | foreach ($path as $i => $value) { 91 | if(! is_array($current)) { 92 | if(! $this->has($current)){ 93 | return null; 94 | } 95 | $current = $this->find($current); 96 | } 97 | if(! isset($current[$value])) { 98 | return null; 99 | } 100 | $current = $current[$value]; 101 | } 102 | return $current; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /tests/Availability/AvailabilityTest.php: -------------------------------------------------------------------------------- 1 | [ [8, 0], [17, 0] ], 18 | 1 => [ [8, 0], [17, 0] ], 19 | 2 => [ [8, 0], [17, 0] ], 20 | 3 => [ [8, 0], [17, 0] ], 21 | 4 => [ [8, 0], [17, 0] ], 22 | 5 => [ [8, 0], [17, 0] ] 23 | ]; 24 | 25 | private $fullRanges = [ 26 | 0 => [ [6, 0], [24, 0] ], 27 | 1 => [ [6, 0], [24, 0] ], 28 | 2 => [ [6, 0], [24, 0] ], 29 | 3 => [ [6, 0], [24, 0] ], 30 | 4 => [ [6, 0], [24, 0] ], 31 | 5 => [ [6, 0], [24, 0] ], 32 | 6 => [ [6, 0], [24, 0] ] 33 | ]; 34 | 35 | // Monday and Wednesday are the same 36 | // Tuesday and Saturday are similar 37 | private $regularWeek = [ 38 | [0, [ 8, 00], [ 8, 50]], 39 | [0, [10, 10], [10, 40]], 40 | [0, [11, 30], [11, 40]], 41 | [0, [12, 00], [13, 00]], 42 | [0, [14, 20], [14, 50]], 43 | [0, [15, 30], [16, 00]], 44 | [0, [16, 40], [17, 00]], 45 | [1, [ 8, 10], [ 8, 30]], 46 | [1, [11, 00], [11, 40]], 47 | [1, [12, 00], [12, 30]], 48 | [1, [13, 00], [13, 50]], 49 | [1, [15, 00], [15, 40]], 50 | [1, [15, 50], [16, 50]], 51 | [2, [ 8, 00], [ 8, 50]], 52 | [2, [10, 10], [10, 40]], 53 | [2, [11, 30], [11, 40]], 54 | [2, [12, 00], [13, 00]], 55 | [2, [14, 20], [14, 50]], 56 | [2, [15, 30], [16, 00]], 57 | [2, [16, 40], [17, 00]], 58 | [3, [ 8, 10], [ 8, 40]], 59 | [3, [ 9, 20], [10, 00]], 60 | [3, [10, 20], [10, 40]], 61 | [3, [12, 30], [13, 10]], 62 | [3, [14, 10], [14, 40]], 63 | [4, [ 8, 40], [ 9, 00]], 64 | [4, [10, 00], [10, 30]], 65 | [4, [12, 50], [14, 40]], 66 | [4, [15, 50], [16, 50]], 67 | [5, [ 8, 00], [ 8, 40]], 68 | [5, [11, 00], [11, 50]], 69 | [5, [13, 00], [13, 50]], 70 | [5, [12, 10], [12, 40]], 71 | [5, [15, 10], [15, 50]], 72 | [5, [15, 50], [16, 50]]]; 73 | 74 | private function apiCall(AvailabilityGenerator $gen, string $json) : string 75 | { 76 | $data = json_decode($json, True); 77 | $coarseness = $data[0]; 78 | $ranges = []; 79 | foreach($data[1] as $dow=>$pair) { 80 | $ranges[intval($dow)] = $pair; 81 | } 82 | $busyList = $data[2]; 83 | return $gen->generateAvailability($busyList, $ranges, $coarseness, null); 84 | } 85 | private function normalize(string $str) : string 86 | { 87 | $str = preg_replace("/quite/", "mostly", $str); 88 | return $str; 89 | } 90 | 91 | 92 | /** 93 | * @test 94 | */ 95 | public function newGenerator() : void 96 | { 97 | $gen = new AvailabilityGenerator(); 98 | $this->assertInstanceOf(AvailabilityGrammar::class, $gen); 99 | } 100 | 101 | /** 102 | * @test 103 | */ 104 | public function emptyWeek() : void 105 | { 106 | $gen = new AvailabilityGenerator(); 107 | $out = $gen->generateAvailability([], $this->ranges, AvailabilityGenerator::SUCCINCT, null); 108 | $this->assertEquals($out, "All week is free all day."); 109 | } 110 | 111 | /** 112 | * @test 113 | */ 114 | public function fullWeek() : void 115 | { 116 | $gen = new AvailabilityGenerator(); 117 | $busyList = []; 118 | foreach($this->ranges as $dow=>$e) { 119 | $busyList[] = [ $dow, $e[0], $e[1] ]; 120 | } 121 | $out = $gen->generateAvailability($busyList, $this->ranges, AvailabilityGenerator::SUCCINCT, null); 122 | $out = $this->normalize($out); 123 | $this->assertEquals($out, "All week is busy all day."); 124 | } 125 | 126 | /** 127 | * @test 128 | */ 129 | public function fullWeirdWeek() : void 130 | { 131 | static::markTestSkipped('The current generator lacks opportunistic aggregation.'); 132 | // currently produces: All week is mostly busy all day. Saturday is busy all day. 133 | 134 | $gen = new AvailabilityGenerator(); 135 | $weirdRanges = [ 136 | 0 => [ [ 8, 0], [17, 0] ], 137 | 1 => [ [ 8, 0], [17, 0] ], 138 | 2 => [ [ 8, 0], [17, 0] ], 139 | 3 => [ [10, 0], [19, 0] ], 140 | 4 => [ [ 8, 0], [17, 0] ], 141 | 5 => [ [ 8, 0], [12, 0] ] 142 | ]; 143 | $busyList = []; 144 | foreach($this->ranges as $dow=>$e) { 145 | $busyList[] = [ $dow, $e[0], $e[1] ]; 146 | } 147 | $out = $gen->generateAvailability($busyList, $weirdRanges, AvailabilityGenerator::SUCCINCT, null); 148 | $out = $this->normalize($out); 149 | $this->assertEquals($out, "All week is busy all day."); 150 | } 151 | 152 | /** 153 | * @test 154 | */ 155 | public function regularWeek() : void 156 | { 157 | $gen = new AvailabilityGenerator(); 158 | mt_srand(4); 159 | $out = $gen->generateAvailability($this->regularWeek, $this->ranges, AvailabilityGenerator::BASE, null); 160 | $out = $this->normalize($out); 161 | 162 | $this->assertEquals($out, "Monday and Wednesday are in the morning mostly free, and from 13 PM to late 16 PM somewhat free; the rest is busy. Tuesday and Saturday are in the early morning busy, from 11 AM to late 13 PM somewhat busy, and from 15 PM to late 16 PM mostly busy; the rest is free. Thursday is in the early morning somewhat free, from 10 AM to half past 12 PM mostly free, and from around 13 PM to 17 PM also mostly free; the rest is busy. Friday is in the morning mostly free, in the mid-afternoon free, and in the late afternoon also free; the rest is busy."); 163 | } 164 | 165 | /** 166 | * @test 167 | */ 168 | public function overlapFiltering() : void 169 | { 170 | $gen = new AvailabilityGenerator(); 171 | mt_srand(4); 172 | $busyList = $this->regularWeek; 173 | foreach($this->ranges as $dow=>$e) { 174 | $busyList[] = [ $dow, $e[0], $e[1] ]; 175 | } 176 | $out = $gen->generateAvailability($busyList, $this->ranges, AvailabilityGenerator::BASE, null); 177 | $out = $this->normalize($out); 178 | $this->assertEquals($out, "All week is busy all day."); 179 | } 180 | 181 | /** 182 | * @test 183 | */ 184 | public function myWeek() : void 185 | { 186 | $gen = new AvailabilityGenerator(); 187 | $busyList = [ 188 | [3, [16, 30], [17, 30] ], 189 | [6, [ 6, 55], [11, 41] ], 190 | [6, [14, 32], [22, 05] ] 191 | ]; 192 | mt_srand(4); 193 | $out = $gen->generateAvailability($busyList, $this->fullRanges, AvailabilityGenerator::SPECIFIC, null); 194 | $out = $this->normalize($out); 195 | 196 | $this->assertEquals($out, "Monday, Tuesday, Wednesday, Friday, and Saturday are free all day. Thursday is free from 6 AM to half past 16 PM, and from half past 17 PM to 24 PM; the rest is busy. Sunday is busy from late 6 AM to late 11 AM, and from half past 14 PM to 22 PM; the rest is free."); 197 | } 198 | 199 | /** 200 | * @test 201 | */ 202 | public function noFreeLunch() : void 203 | { 204 | $gen = new AvailabilityGenerator(); 205 | $out = $this->apiCall($gen, '[1,{"0":[[9,0],[17,0]],"1":[[9,0],[17,0]],"2":[[9,0],[17,0]],"3":[[9,0],[17,0]],"4":[[9,0],[17,0]],"5":[[9,0],[17,0]]},[[2,[12,0],[12,30]],[2,[12,30],[13,0]]]]'); 206 | $out = $this->normalize($out); 207 | $this->assertEquals($out, "The mornings are free in the morning. The afternoons, Wednesday is free from 13:00 PM to 17:00 PM. Monday, Tuesday, Thursday, Friday, and Saturday are free in the afternoon."); 208 | } 209 | 210 | /** 211 | * @test 212 | */ 213 | public function morningPerson() : void 214 | { 215 | static::markTestSkipped('The morning needs to be ommitted.'); 216 | // currently produces: The mornings are somewhat busy in the morning. The afternoons, Monday, Wednesday, Thursday, Friday, and Saturday are quite free. 217 | 218 | $gen = new AvailabilityGenerator(); 219 | $out = $this->apiCall($gen, '[1,{"0":[[9,0],[17,0]],"1":[[9,0],[17,0]],"2":[[9,0],[17,0]],"3":[[9,0],[17,0]],"4":[[9,0],[17,0]],"5":[[9,0],[17,0]]},[[0,[11,30],[12,0]],[2,[11,30],[12,0]],[1,[11,30],[12,0]],[1,[12,30],[13,0]],[4,[12,30],[13,0]],[4,[13,30],[14,0]],[3,[13,30],[14,0]],[2,[14,30],[15,0]],[1,[14,30],[15,0]],[1,[15,30],[16,0]],[2,[15,30],[16,0]],[3,[14,30],[15,0]],[3,[10,30],[11,0]],[3,[9,30],[10,0]],[3,[15,30],[16,0]],[2,[12,30],[13,0]],[0,[12,30],[13,0]],[4,[11,30],[12,0]],[5,[11,30],[12,0]],[0,[10,30],[11,0]],[1,[10,30],[11,0]],[2,[10,30],[11,0]],[4,[10,30],[11,0]],[5,[10,30],[11,0]],[0,[9,30],[10,0]],[1,[9,30],[10,0]],[2,[9,30],[10,0]],[4,[9,30],[10,0]],[5,[9,30],[10,0]],[3,[11,30],[12,0]],[3,[12,30],[13,0]],[5,[12,30],[13,0]],[5,[13,30],[14,0]],[2,[13,30],[14,0]],[1,[13,30],[14,0]],[0,[13,30],[14,0]],[1,[13,0],[13,30]],[1,[12,0],[12,30]],[1,[11,0],[11,30]]]]'); 220 | $out = $this->normalize($out); 221 | // what happened with Tuesday??? 222 | $this->assertEquals($out, "The mornings are somewhat busy. The afternoons, Monday, Wednesday, Thursday, Friday, and Saturday are quite free."); 223 | } 224 | 225 | /** 226 | * @test 227 | */ 228 | public function randomBroke() : void 229 | { 230 | $gen = new AvailabilityGenerator(); 231 | $out = $this->apiCall($gen, '[3,{"0":[[9,0],[17,0]],"1":[[9,0],[17,0]],"2":[[9,0],[17,0]],"3":[[9,0],[17,0]],"4":[[9,0],[17,0]],"5":[[9,0],[17,0]]},[[2,[16,0],[16,30]],[2,[16,30],[17,0]],[2,[17,0],[17,30]],[1,[13,0],[13,30]],[4,[13,0],[13,30]],[4,[14,30],[15,0]],[4,[13,30],[14,0]],[5,[12,0],[12,30]],[5,[11,0],[11,30]],[5,[10,30],[11,0]]]]'); 232 | $out = $this->normalize($out); 233 | $this->assertEquals($out, "Monday and Thursday are free all day. Tuesday is free from 9:00 AM to 13:00 PM, and from 13:30 PM to 17:00 PM; the rest is busy. Wednesday is free from 9:00 AM to 16:00 PM. Friday is free from 9:00 AM to 13:00 PM, from 14:00 PM to 14:30 PM, and from 15:00 PM to 17:00 PM; the rest is busy. Saturday is free from 9:00 AM to 10:30 AM, from 11:30 AM to 12:00 PM, and in the afternoon; the rest is busy."); 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /tests/GeneratorTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Generator::class, GeneratorTest::$gen); 28 | } 29 | 30 | /** 31 | * @test 32 | */ 33 | public function testA() : void 34 | { 35 | $str = GeneratorTest::$gen->generate("A"); 36 | $this->assertEquals($str, "got: 1"); 37 | } 38 | 39 | /** 40 | * @test 41 | */ 42 | public function testB() : void 43 | { 44 | $str = GeneratorTest::$gen->generate("B"); 45 | $this->assertEquals($str, "got: 2, 3"); 46 | } 47 | 48 | /** 49 | * @test 50 | */ 51 | public function testX() : void 52 | { 53 | $str = GeneratorTest::$gen->generate("X"); 54 | $this->assertEquals($str, "got: 2, then got: 2"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/LexiconTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Lexicon::class, $lex); 21 | } 22 | 23 | /** 24 | * @test 25 | */ 26 | public function bareKeys() : void 27 | { 28 | $lex = new Lexicon(null, '{"test":"sample"}'); 29 | $this->assertEquals($lex->string_for_id("test"), "sample"); 30 | } 31 | 32 | /** 33 | * @test 34 | */ 35 | public function has() : void 36 | { 37 | $lex = new Lexicon(null, '{"test":"sample"}'); 38 | $this->assertTrue($lex->has("test")); 39 | } 40 | 41 | /** 42 | * @test 43 | */ 44 | public function query() : void 45 | { 46 | $lex = new Lexicon(null, '{"test": [ { "string":"sample1", "pos":"1"}, {"string":"sample2", "pos":"2"}]}'); 47 | $this->assertEquals($lex->query([ "id"=>"test", "pos"=>"2"])[0]['string'], "sample2"); 48 | } 49 | 50 | /** 51 | * @test 52 | */ 53 | public function sample() : void 54 | { 55 | $lex = new Lexicon(null, '{"test": [ { "string":"sample1", "pos":"1"}, {"string":"sample2", "pos":"2"}]}'); 56 | $seen = []; 57 | foreach(range(0,100) as $x) { 58 | $seen[$lex->string_for_id("test")] = 1; 59 | } 60 | $this->assertEquals(count($seen), 2); 61 | } 62 | 63 | /** 64 | * @test 65 | */ 66 | public function template() : void 67 | { 68 | $lex = new Lexicon(null, '{"template": { "mixed": [ "text ", { "slot":"a" }, " text2 ", { "slot":"c" } ] } }'); 69 | $this->assertEquals($lex->string_for_id("template", ["a"=>1,"b"=>2,"c"=>3]), "text 1 text2 3"); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /tests/OntologyTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Ontology::class, $onto); 21 | } 22 | 23 | /** 24 | * @test 25 | */ 26 | public function has() : void 27 | { 28 | $onto = new Ontology('{"test":{"class":"sample"}}'); 29 | $this->assertTrue($onto->has("test")); 30 | } 31 | 32 | /** 33 | * @test 34 | */ 35 | public function find_all_by_class() : void 36 | { 37 | $onto = new Ontology('{"test1":{"class":"sample"},"test2":{"class":"sample2"},"test3":{"class":"sample"}}'); 38 | $this->assertEquals(count($onto->find_all_by_class("sample")),2); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/SimpleGenerator.php: -------------------------------------------------------------------------------- 1 | a(1); 13 | case "B": return $this->b(2,3); 14 | default: return $this->z(2); 15 | } 16 | } 17 | 18 | protected function a($a) 19 | { 20 | return "got: $a"; 21 | } 22 | 23 | protected function b($b1, $b2) 24 | { 25 | return "got: $b1, $b2"; 26 | } 27 | 28 | protected function z($z) 29 | { 30 | return "got: $z, then ".$this->a($z); 31 | } 32 | } 33 | 34 | --------------------------------------------------------------------------------