{"id":16100677,"url":"https://github.com/picimako/drupal-paragraph-context","last_synced_at":"2026-05-17T15:05:02.828Z","repository":{"id":53190509,"uuid":"217067452","full_name":"picimako/drupal-paragraph-context","owner":"picimako","description":"Drupal (and CMS in general) content assembly from special tree view and table view formats.","archived":false,"fork":false,"pushed_at":"2023-06-14T22:29:47.000Z","size":145,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-29T06:29:54.542Z","etag":null,"topics":["cms","drupal","e2e-tests","gherkin"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/picimako.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-23T13:41:37.000Z","updated_at":"2021-04-01T19:02:19.000Z","dependencies_parsed_at":"2024-12-19T06:42:27.372Z","dependency_job_id":"1fd3eed0-a572-49e5-94c0-9e7f43f7f2a4","html_url":"https://github.com/picimako/drupal-paragraph-context","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/picimako/drupal-paragraph-context","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/picimako%2Fdrupal-paragraph-context","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/picimako%2Fdrupal-paragraph-context/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/picimako%2Fdrupal-paragraph-context/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/picimako%2Fdrupal-paragraph-context/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/picimako","download_url":"https://codeload.github.com/picimako/drupal-paragraph-context/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/picimako%2Fdrupal-paragraph-context/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33143276,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T09:28:26.183Z","status":"ssl_error","status_checked_at":"2026-05-17T09:27:52.702Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cms","drupal","e2e-tests","gherkin"],"created_at":"2024-10-09T18:47:42.022Z","updated_at":"2026-05-17T15:04:57.819Z","avatar_url":"https://github.com/picimako.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Drupal Paragraph Context based content creation\n\n## Purpose of this project\n\nThis project is rather a case study, and I can image using it only in specific cases but the idea behind it might be\nmore important and more reusable than this implementation of it.\n\n## Background and how this works\n\nThis idea came up during and a similar solution was originally made for a Drupal/PHP based project\nwhich used Paragraphs and Modifiers to build certain types of pages.\n\nIt turned out that creating simple and meaningful CSS selectors is not the easiest thing in the native Drupal\neditor because most of the HTML classes, ids and such are generated and don't really give meaningful context. Also when\ntargeting a certain button, dropdown, etc. it can happen that different buttons have the same selectors without their\ncontext, so simply targeting that single element is not enough.\n\nThat's where this project comes in. The initial idea and solution was to handle each item on the editor form contextually.\nIn order to be able to properly target them, construct a CSS selector that also includes selectors for their parent elements.\n\nIn its initial form it needed manual insertion of Gherkin steps configuring this context before each step\nthat interacted with the editor. That looked something like this:\n\n```gherkin\nWhen I add an IMAGE component\nAnd the context is CONTAINER \u003e LAYOUT \u003e IMAGE\nAnd I select the 1st image from the library\nAnd I configure the \"https://duckduckgo.com\" url in the image\nWhen I add a RICH_TEXT component\nAnd the context is CONTAINER \u003e LAYOUT \u003e RICH_TEXT\n...\n``` \n\nThe step definitions that selected and configured a component and interacted with the elements of the a given paragraph\nwere updated in a way to use the last component context selector that was configured. So in this example both steps that\nconfigure the image know about the context selector converted from `CONTAINER \u003e LAYOUT \u003e IMAGE`.\n\nThough it may double the number of steps in gherkin files, and clutters them making it harder to see what is actually\nrelevant to the feature and functionality tested, for a while it worked just fine.\n\nHowever we reached a point when we needed to validate complex nested components like carousels and galleries, and it\nbecame harder to read gherkin files, and they got even longer due to the fact how many steps it involves to\nhave a scenario creating such nested components.\n\nI know BDD may not necessarily the best option to implement tests for a CMS but the client didn't care and we found BDD\ntests easier to read and maintain.\n\n### Simplify with tree view based page creation \n\nThen came the idea to simplify the content creation big time, make the gherkin files much shorter, more readable, so\ncame this solution, to just write the structure of the content as a tree view (including the configuration of the\ncomponents), let it be parsed, and based on the nodes in the tree, execute the addition and configuration of the content\nand the components in it.\n\nAs a result we still have a Selenium test creating a Drupal page but the gherkin files are much simpler and shorter.\nThe previous example would look something like this:\n\n```gherkin\nGiven the following page\n\"\"\"\n- CONTAINER\n-- LAYOUT    \u003c- This a Component node.\n--- IMAGE\n---* index:1, url:https://duckduckgo.com  \u003c- This is a Configuration node.\n--- RICH_TEXT\n----@ ABSOLUTE_HEIGHT_MODIFIER   \u003c- This is also a Component Node but for a Modifier.\n\"\"\"\n```\n\nTo give you insight about all the possible combinations of Component and Configuration nodes, please refer to the example below:\n\n```gherkin\nGiven the following page\n\"\"\"\n- CONTAINER \u003c- This is a Component node at root (1) level.\n-- LAYOUT\n--- IMAGE   \u003c- This is also a Component node at the 3rd level.\n--- RICH_TEXT \u003e\u003e type:\"Full HTML\"     \u003c- This is a Component node with an inline configuration.\n---* text: some text\n-- LAYOUT\n--- CAROUSEL\n---- VIDEO\n----* url:https://some.url, initialTime:16    \u003c- This is a Configuration node.\n                                                 The level marker is only for consistency with the rest of the tree.\n---- VIDEO\n----* url:https://some.other/url\n----* initialTime:10     \u003c- Configurations can be defined in multiple rows for the same component, and are handled as separate Configuration nodes.\n-----@ ABSOLUTE_HEIGHT_MODIFIER    \u003c- This is a Modifier Component node for the last VIDEO component.\n\"\"\"\n```\n\nThe underlying logic no more uses the context selector path (*CONTAINER \u003e LAYOUT \u003e IMAGE*), instead it constructs\ndirectly the CSS selector from the tree defined above when it is being traversed.\n\nThe logic differentiates the following two nodes:\n- **Component node**: they represent Drupal Paragraphs and Modifiers, and are used for building the component context\n                  (saved in a component tree), and also signals to the parser that it should add a new component\n                  at that point. It can have an optional inline configuration (only for tree view based content layout)\n                  as well which is a way to create more concise layouts.\n\n- **Configuration node**: they are basically a key-value mapping, so that components can be configured based on them.\n                      They are not saved in the component tree, they are used only to invoke configuration logic at certain\n                      points.\n\nOf course passing data from configuration nodes may need additional type conversion if they expect/use some of the parameter\ntype converters from the BDD library at hand, or even more, data table type converters.\n\nAnd to be able to do something with this whole library, the entry point is `ComponentTreeBasedContentAssembler.assembleContent(String)`.\n\n#### Configuration node format\n\nThe followings are configuration node values listing which ones are valid and which ones are invalid:\n\n#### Valid values\n\n**Unquoted**\n- ---* url:\n- ---* url: (with a whitespace at the end)\n- \\* url:something\n- ---* url: something\n- ---* url: something, color:\n- ---* url: something, color: (with a whitespace at the end)\n- ---* url: something, color: rgba(0\\\\,0\\\\,0\\\\,0)\n\n**Quoted**\n- ---* url:\" \"\n- ---* url:\" something\"\n- ---* url:\" something\", color:\n- ---* url:\" something\", color:\" \"\n- ---* url:\" something\", color:\" rgba(0\\\\,0\\\\,0\\\\,0)\"\n\n#### Invalid values\n\n- ---*\n- ---* (with a whitespace at the end)\n- ---* ,\n- ---* url\n- ---* url: ,\n- ---* url: something,\n- ---* url: something, (with a whitespace at the end)\n- ---* url: something, color\n\n## Data table based implementation\n\nThis is an alternate and more concise version of the tree view based implementation. It relies on passing a data table\nto the entry point of this solution: `TableBasedContentAssembler.assembleContent(List)`.\n\nIn this table based variant it is clearer which configuration is for which component and may even halve the length of\nsuch input data.\n\nThe table is expected to be in the following format:\n```gherkin\n| Component             | Configuration                         |  \u003c- Header row\n| \u003c                     | title:\"Some page title\"               |  \u003c- Root level configuration\n|                       | meta-keywords:keywords                |\n| \u003e CONTAINER           | bg:#fff                               |  \u003c- Component with configuration\n| \u003e\u003e LAYOUT             |                                       |  \u003c- Component without configuration\n| \u003e\u003e\u003e IMAGE             | name:some-image.png                   |\n|                       | link:/some/path                       |  \u003c- Configuration for the last defined component, in this case IMAGE\n| \u003e\u003e\u003e YOUTUBE_VIDEO     | title:\"Good title\", features:autoplay |  \u003c- Component with multiple configurations at once\n| \u003e\u003e\u003e@ PADDING_MODIFIER | left:50px                             |  \u003c- Modifier with configuration\n```\n\nThere is support for root-level configuration by defining the component as `\u003c` but it is considered valid only in the first\ndata row of the table.\n\nThere is also support for configurations to be defined in multiple table rows, in that case only the first occurrence\nof the entry must include the component definition like this:\n\n```gherkin\n| \u003e CONTAINER | bg:#fff             |\n| \u003e\u003e LAYOUT   |                     |\n| \u003e\u003e\u003e IMAGE   | name:some-image.png |\n|             | link:/some/path     |\n```\n\nThe last two configurations are both applied to the same image component in this case.\n\nHowever, if the table would be defined as:\n\n```gherkin\n| \u003e CONTAINER | bg:#fff             |\n| \u003e\u003e LAYOUT   |                     |\n| \u003e\u003e\u003e IMAGE   | name:some-image.png |\n| \u003e\u003e\u003e IMAGE   | link:/some/path     |\n```\n\nthere would be two image components added to the layout, and the last two configurations would be applied to two\ndifferent image components.\n\nThe list of entries are provided to the content assembler class as a list of `ComponentAndConfiguration` objects for\nwhich one might have to introduce a kind of data table type converter.\n\nThere is one another difference between this and the tree view based variants that in this case configurations\ndon't have a prefix at all. They are simply defined in the right-hand side column.\n\n## Converters\n\nIt might happen that you want to migrate from one component layout representation to the other,\nand for that purpose there is one converter for now to transition from the Tree View to the Table View layout.\n\n**Usage:**\n- download and copy the standalone `lib/converter/converter.jar` file to a folder\n- copy the component layouts (without any Gherkin specific surroundings) to text files.\n    - each tree view layout should be copied to a separate text file\n- copy these text files into the folder where the jar file is\n- run the jar file\n    - it accepts one or more arguments, the filenames of the text files to convert\n    - e.g. `java .jar converter.jar tree1.txt tree2.txt`\n\nAs a result it will create separate text files for each source file, each having its name prefixed with `converted_`.\n\nThe class that initiates the conversion is at `io.picimako.drupal.context.converter.TreeViewToDataTableConversionExecutor`,\nwhile the class that contains the actual conversion logic is `io.picimako.drupal.context.converter.TreeViewToDataTableConverter`.\n\n## Additional notes, caveats\n\n- It is worth keeping in mind that depending on the structure of your project some classes may need to be moved to\ndifferent scope (test/compile), different packages or even modules to make them available properly.\n- Passing complex data types such as tables in a configuration property is not possible in a proper way (only by inventing\nsome custom one-liner pattern which may be parsed accordingly).\n\n## Credits\n\nThe idea for the data table version came from a friend of mine, so a big thank you goes to\n[limpek07](https://github.com/Limpek07) for that.\n\n## Future plans\n\nFor easy migration between the two formats:\n- Create data table format to tree layout converter.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpicimako%2Fdrupal-paragraph-context","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpicimako%2Fdrupal-paragraph-context","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpicimako%2Fdrupal-paragraph-context/lists"}