diff --git a/.idea/runConfigurations/run_example_prepare_.xml b/.idea/runConfigurations/run_example_prepare_.xml new file mode 100644 index 0000000000000000000000000000000000000000..f41e2e8273598279ea7b8ea6f7c92e6bcc474d28 --- /dev/null +++ b/.idea/runConfigurations/run_example_prepare_.xml @@ -0,0 +1,16 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="run example prepare " type="GoApplicationRunConfiguration" factoryName="Go Application"> + <module name="bob" /> + <working_directory value="$PROJECT_DIR$/../../alvine/local-dev/components/alvine/apps/test/source" /> + <parameters value="--verbose template prepare --input $PROJECT_DIR$/development/examples/example1/template/ --output $PROJECT_DIR$/development/examples/example1/build --data-file=$PROJECT_DIR$/development/examples/example1/pages/en.yaml" /> + <EXTENSION ID="com.fapiko.jetbrains.plugins.better_direnv.runconfigs.GolandRunConfigurationExtension"> + <option name="DIRENV_ENABLED" value="false" /> + <option name="DIRENV_TRUSTED" value="false" /> + </EXTENSION> + <kind value="PACKAGE" /> + <package value="gitlab.schukai.com/oss/bob" /> + <directory value="$PROJECT_DIR$" /> + <filePath value="$PROJECT_DIR$/application/source/main.go" /> + <method v="2" /> + </configuration> +</component> \ No newline at end of file diff --git a/README.md b/README.md index 02d1f5f1eee7383c82902554086de27d870e9207..bbc39eed756e6af2e53904dc5815f5ea21150517 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Bob -bob is an HTML and HTML fragment builder +Bob is an HTML and HTML fragment builder ## Documentation @@ -14,7 +14,8 @@ wget -O ~/.local/bin/bob http://download.schukai.com/tools/bob/bob-$( uname -s | ### Nix/Flake Support -This repository contains a file called flake.nix. You can install this program using the nix package manager. +This repository contains a file called flake.nix. You can install this program +using the [nix package manager](https://nixos.org/). ## Usage @@ -27,7 +28,7 @@ bob template prepare --input ./templates/ --output ./output/ --data-file ./data. ``` This will create files in the `./output/` directory with all parsed templates from `./templates/` directory. -Also, a data YAML. This data YAML is used to generate the final files. +Also, a data YAML. This data YAML is used to generate the final files with the `bob html generate` command. This command prepares the title, description, keywords, and other metadata for the templates. Furthermore, it will parse the templates for images, anchors, and text. @@ -43,6 +44,21 @@ If the argument `--data-file` is not set, the data YAML will be written to `./ou | `<a href="https://gitlab.schukai.com/oss/bob">` | `<a href="https://gitlab.schukai.com/oss/bob" data-attributes="href path:a.id1004.href">` | | `<p>Bob is a html and html fragment builder</p>` | `<p><span data-attributes="text path:p.id1005.text">Bob is a html and html fragment builder</span></p>` | +If you want to translate the text, you can copy the default `data.yaml` to a new file and translate the text there. + +A good practice is to use the language code as the file name. +For example, `de.yaml` for German, `en.yaml` for English, etc. + +Beside text, images and metadata, special attributes are also extracted. +For example, the Monster datatable headers `data-monster-head` are extracted. + +```html +<monster-datatable> + <template id="datatable-order-list-row"> + <div data-monster-head="OID" ...></a></div> + ... +``` + #### HTML @@ -54,9 +70,10 @@ will be processed. ```bash -bob html generate --input ./input/ --output ./output/ +bob html generate --input ./input/ --output ./output/ --data-files ./pages/ ``` +If the `--data-files' attribute is not defined, the `--input' directory is used. The yaml looks like: @@ -117,8 +134,8 @@ test1.html: The translations are set in a json inside a script tag. -The modifications run last. Here you can remove tags, add inhaklt and set attributes. +The `modifications' rules are executed last. Here you can remove tags, add content and set attributes. ##### Sync diff --git a/application/source/command.go b/application/source/command.go index fdec0b7baf1d9fa3160482dc972da577985379a3..e4061c2cd1540c0f67856db182fa01ebf4bf61c0 100644 --- a/application/source/command.go +++ b/application/source/command.go @@ -20,13 +20,14 @@ type Definition struct { Prepare struct { Input string `short:"i" long:"input" description:"Directory with html files to prepare" required:"true"` Output string `short:"o" long:"output" description:"Directory to save prepared html files" required:"true"` - DataFile string `short:"d" long:"data-file" description:"Name of the data file to use, default is data.yaml"` + DataFile string `short:"d" long:"data-file" description:"Name of the main data file" default:"data.yaml"` } `command:"prepare" description:"Prepare content from a file" call:"PrepareTemplate"` } `command:"template" description:"Template commands"` HTML struct { Generate struct { - Input string `short:"i" long:"input" description:"Directory with prepared html files" required:"true"` - Output string `short:"o" long:"output" description:"Directory to save generated template files" required:"true"` + Input string `short:"i" long:"input" description:"Directory with prepared html files" required:"true"` + Output string `short:"o" long:"output" description:"Directory to save generated template files" required:"true"` + DataFiles string `short:"d" long:"data-files" description:"Directory with data files" required:"true"` } `command:"generate" description:"Generate html files from a file" call:"GenerateHTML"` Sync struct { Specification string `short:"s" long:"specification" description:"Specification file" required:"true"` @@ -55,7 +56,11 @@ func (d *Definition) SyncHTML(s *xflags.Settings[Definition]) { } func (d *Definition) GenerateHTML(s *xflags.Settings[Definition]) { - err := filepath.Walk(d.HTML.Generate.Input, func(p string, info os.FileInfo, err error) error { + if d.HTML.Generate.DataFiles == "" { + d.HTML.Generate.DataFiles = d.HTML.Generate.Input + } + + err := filepath.Walk(d.HTML.Generate.DataFiles, func(p string, info os.FileInfo, err error) error { if err != nil { return err @@ -70,7 +75,7 @@ func (d *Definition) GenerateHTML(s *xflags.Settings[Definition]) { return nil } - return html2.GenerateFiles(p, d.HTML.Generate.Output) + return html2.GenerateFiles(p, d.HTML.Generate.Input, d.HTML.Generate.Output) }) if err != nil { diff --git a/application/source/constants/attributes.go b/application/source/constants/attributes.go new file mode 100644 index 0000000000000000000000000000000000000000..c383e7bf800c4da62712f0758c0d9024867772f6 --- /dev/null +++ b/application/source/constants/attributes.go @@ -0,0 +1,3 @@ +package constants + +const DataBobReferenceAttributeKey = "data-bob-reference" diff --git a/application/source/html/generate.go b/application/source/html/generate.go index 185c10b953b9fee669601b1e2793e6a88b447e91..40c579600618cfc71caf8cd97a4c98b194f057d3 100644 --- a/application/source/html/generate.go +++ b/application/source/html/generate.go @@ -12,9 +12,9 @@ import ( "strings" ) -func GenerateFiles(dataPath, out string) error { +func GenerateFiles(dataPath, templates, out string) error { - dir := path.Dir(dataPath) + templatesDir := path.Dir(templates) yamlFile, err := os.ReadFile(dataPath) if err != nil { @@ -27,7 +27,7 @@ func GenerateFiles(dataPath, out string) error { } for name, page := range storage { - p := path.Join(dir, name) + p := path.Join(templatesDir, name) generatedHtml, err := Generate(page, p) if err != nil { return err diff --git a/application/source/template/prepare.go b/application/source/template/prepare.go index fb7fd548e0f6a9da93883693ddefdd515de43df8..b13d0fb5bb00defac2679f8a92ffb16f89f47134 100644 --- a/application/source/template/prepare.go +++ b/application/source/template/prepare.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "github.com/andybalholm/cascadia" + "gitlab.schukai.com/oss/bob/constants" "gitlab.schukai.com/oss/bob/types" "gitlab.schukai.com/oss/bob/util" "golang.org/x/net/html" @@ -173,11 +174,11 @@ func prepareAnchors(node *html.Node, storage *types.PageData) { hreflang := util.GetAttribute(n.Attr, "hreflang") href := util.GetAttribute(n.Attr, "href") - id := util.GetOrCreateId(n, title+hreflang+href) + id := util.GetOrCreateReference(n, title+hreflang+href) if id == "" { id, _ = util.RandomString(8) - n.Attr = removeAttribute(n.Attr, "id") - n.Attr = append(n.Attr, html.Attribute{Key: "id", Val: id}) + n.Attr = removeAttribute(n.Attr, constants.DataBobReferenceAttributeKey) + n.Attr = append(n.Attr, html.Attribute{Key: constants.DataBobReferenceAttributeKey, Val: id}) } setDataAttributesAttribute(n, attributeAttributes, "href", "path:anchors."+id+".href") @@ -231,11 +232,11 @@ func prepareImages(node *html.Node, storage *types.PageData) { title := util.GetAttribute(n.Attr, "title") source := util.GetAttribute(n.Attr, "src") - id := util.GetOrCreateId(n, alt+title+source) + id := util.GetOrCreateReference(n, alt+title+source) if id == "" { id, _ = util.RandomString(8) - n.Attr = removeAttribute(n.Attr, "id") - n.Attr = append(n.Attr, html.Attribute{Key: "id", Val: id}) + n.Attr = removeAttribute(n.Attr, constants.DataBobReferenceAttributeKey) + n.Attr = append(n.Attr, html.Attribute{Key: constants.DataBobReferenceAttributeKey, Val: id}) } setDataAttributesAttribute(n, attributeAttributes, "src", "path:content."+id+".src") @@ -284,7 +285,7 @@ func prepareTranslationJson(node *html.Node, storage *types.PageData) { for _, n := range list { - id := util.GetAttribute(n.Attr, "id") + id := util.GetAttribute(n.Attr, constants.DataBobReferenceAttributeKey) typ := util.GetAttribute(n.Attr, "type") n.Attr = removeAttribute(n.Attr, attributeReplace) @@ -377,19 +378,7 @@ func runNodes(n *html.Node, storage *types.PageData) { } -func checkNodes(n *html.Node, storage *types.PageData) { - if n.Parent != nil { - if n.Parent.Type == html.ElementNode { - if n.Parent.Data == "script" || n.Parent.Data == "style" { - return - } - } - } - - if n.Type != html.TextNode { - return - } - +func handleTextNode(n *html.Node, storage *types.PageData) { content := strings.TrimSpace(n.Data) if content == "" { return @@ -406,7 +395,7 @@ func checkNodes(n *html.Node, storage *types.PageData) { Data: atom.Span.String(), Attr: []html.Attribute{ { - Key: "id", + Key: constants.DataBobReferenceAttributeKey, Val: id, }, { @@ -424,6 +413,58 @@ func checkNodes(n *html.Node, storage *types.PageData) { Id: id, Text: content, }) +} + +func checkNodes(n *html.Node, storage *types.PageData) { + if n.Parent != nil { + if n.Parent.Type == html.ElementNode { + if n.Parent.Data == "script" || n.Parent.Data == "style" { + return + } + } + } + + if n.Type == html.TextNode { + handleTextNode(n, storage) + } else if n.Type == html.ElementNode { + switch n.Data { + case "monster-datatable": + checkMonsterDatatableHead(n, storage) + } + + } + +} + +func checkMonsterDatatableHead(n *html.Node, storage *types.PageData) { + selector, err := cascadia.Parse("[data-monster-head]") + if err != nil { + return + } + + list := cascadia.QueryAll(n, selector) + if list == nil { + return + } + + for _, div := range list { + id := util.GetAttribute(div.Attr, constants.DataBobReferenceAttributeKey) + if id == "" { + id = util.GetNextId() + div.Attr = append(div.Attr, html.Attribute{Key: constants.DataBobReferenceAttributeKey, Val: id}) + } + + head := util.GetAttribute(div.Attr, "data-monster-head") + + div.Attr = removeAttribute(div.Attr, "data-attributes") + div.Attr = append(div.Attr, html.Attribute{Key: "data-attributes", Val: "data-monster-head path:text." + id + ".text"}) + + storage.Text = append(storage.Text, types.Text{ + Id: id, + Text: head, + }) + + } } diff --git a/application/source/util/html.go b/application/source/util/html.go index 26753f2620c1e2df87698b496c5bfac11ecd075d..d206b967618415d1f1384f24e327e70b7ab3fe7d 100644 --- a/application/source/util/html.go +++ b/application/source/util/html.go @@ -2,6 +2,7 @@ package util import ( "errors" + "gitlab.schukai.com/oss/bob/constants" "golang.org/x/net/html" "os" "path" @@ -37,11 +38,11 @@ func GetNextId() string { } -func GetOrCreateId(node *html.Node, text string) string { +func GetOrCreateReference(node *html.Node, text string) string { var err error - nodeId := GetAttribute(node.Attr, "id") + nodeId := GetAttribute(node.Attr, constants.DataBobReferenceAttributeKey) if nodeId != "" { return nodeId } @@ -51,7 +52,7 @@ func GetOrCreateId(node *html.Node, text string) string { nodeId = GetNextId() } - node.Attr = append(node.Attr, html.Attribute{Key: "id", Val: nodeId}) + node.Attr = append(node.Attr, html.Attribute{Key: constants.DataBobReferenceAttributeKey, Val: nodeId}) return nodeId } diff --git a/development/examples/example1/template/data.yaml b/development/examples/example1/pages/en.yaml similarity index 69% rename from development/examples/example1/template/data.yaml rename to development/examples/example1/pages/en.yaml index 5b0608a6edac4a5f9af57410832ec6ae93b74a75..d7c632d306c83faed8c736fd8870fe60533318cc 100755 --- a/development/examples/example1/template/data.yaml +++ b/development/examples/example1/pages/en.yaml @@ -1,3 +1,83 @@ +test.html: + export: en/test.html + lang: en + title: Bad Request + meta: + author: schukai GmbH + description: The request was malformed or invalid. + images: + - id: tickyesdata-image-gi-4013311193 + source: |- + data:image/gif;base64,R0lGODdhEAAQAMwAAPj7+FmhUYjNfGuxYYDJdYTIeanOpT+DOTuANXi/bGOrWj6CONzv2sPjv2Cm + V1unU4zPgI/Sg6DJnJ3ImTh8Mtbs00aNP1CZSGy0YqLEn47RgXW8amasW7XWsmmvX2iuXiwAAAAAEAAQAAAFVyAgjmRpnihqGCkpDQ + PbGkNUOFk6DZqgHCNGg2T4QAQBoIiRSAwBE4VA4FACKgkB5NGReASFZEmxsQ0whPDi9BiACYQAInXhwOUtgCUQoORFCGt/g4QAIQA7 + alt: tick + title: "yes" + anchors: + - id: yes-a-html + href: /a.html + hreflang: "" + title: "Yes" + - id: QPCI6WO0 + href: "" + hreflang: "" + title: "" + text: + - text: Bad Request + id: bad-request + - text: The request was incorrect, the server could not process it. + id: the-request-was-inco-2640993422 + - text: Try submitting your + id: try-submitting-your + - text: request + id: request + - text: again (sometimes this helps). + id: again-sometimes-this-2086042570 + - text: Contact support if you continue to have problems. + id: contact-support-if-y-3404332025 + - text: |- + If you received this message as a result of a request to the server API, then check the structure + against the documentation. + id: if-you-received-this-423958995 + - text: 'You can try the following steps:' + id: you-can-try-the-foll-3363859033 + - text: OID + id: id1000 + - text: date + id: id1001 + - text: username + id: id1002 + - text: customer + id: id1003 + - text: zipcode + id: id1004 + - text: city + id: id1005 + - text: country + id: id1006 + - text: street + id: id1007 + - text: order state + id: id1008 + - text: workflow state + id: id1009 + - text: total + id: id1010 + - text: company + id: id1011 + - text: channel order number + id: id1012 + - text: RESUBMISSIONDATE!! + id: id1013 + - text: payment type + id: id1014 + - text: ',' + id: id1015 + translations: [] + modifications: + remove: [] + add: [] + attributes: [] test1.html: export: de/test1.html lang: de @@ -44,7 +124,7 @@ test1.html: - text: 'You can try the following steps:' id: you-can-try-the-foll-3363859033 translations: - - id: translations + - id: "" type: application/json translations: key1: value1 @@ -53,15 +133,9 @@ test1.html: one: value3 two: value4 modifications: - remove: - - .gradient - add: - - selector: .infobox - html: <b><span>GURKE</span></b> - attributes: - - selector: '#mainscript' - name: type - value: YES + remove: [] + add: [] + attributes: [] test2.html: export: en/test2.html lang: en @@ -167,7 +241,7 @@ test4.html: - text: one-time password id: one-time-password - text: . - id: id1000 + id: id1016 translations: [] modifications: remove: [] diff --git a/development/examples/example1/template/test.html b/development/examples/example1/template/test.html index ab678048edbb1e41bb68cf5c18d65e7381b1378f..3508e76987eb54184d65f764bbce258bf3d18786 100644 --- a/development/examples/example1/template/test.html +++ b/development/examples/example1/template/test.html @@ -40,26 +40,26 @@ <img width="16" height="16" alt="tick" title="yes" src="data:image/gif;base64,R0lGODdhEAAQAMwAAPj7+FmhUYjNfGuxYYDJdYTIeanOpT+DOTuANXi/bGOrWj6CONzv2sPjv2Cm V1unU4zPgI/Sg6DJnJ3ImTh8Mtbs00aNP1CZSGy0YqLEn47RgXW8amasW7XWsmmvX2iuXiwAAAAAEAAQAAAFVyAgjmRpnihqGCkpDQ PbGkNUOFk6DZqgHCNGg2T4QAQBoIiRSAwBE4VA4FACKgkB5NGReASFZEmxsQ0whPDi9BiACYQAInXhwOUtgCUQoORFCGt/g4QAIQA7" id="tickyesdata-image-gi-4013311193" data-attributes="alt path:content.tickyesdata-image-gi-4013311193.alt,src path:content.tickyesdata-image-gi-4013311193.src,title path:content.tickyesdata-image-gi-4013311193.title"/> - <h1 class="deco"><span id="bad-request" data-replace-self="path:text.bad-request.text">Bad Request</span></h1> - <p><span id="the-request-was-inco-2640993422" data-replace-self="path:text.the-request-was-inco-2640993422.text">The request was incorrect, the server could not process it. - </span></p><p> + <h1 class="deco">Bad Request</h1> + <p>The request was incorrect, the server could not process it. + </p><p> </p><div class="infobox"> <div> <ul> - <li><span id="try-submitting-your" data-replace-self="path:text.try-submitting-your.text">Try submitting your </span><a href="/a.html" title="Yes" id="yes-a-html" + <li>Try submitting your<a href="/a.html" title="Yes" id="yes-a-html" data-attributes="href path:anchors.yes-a-html.href,title path:anchors.yes-a-html.title,hreflang path:anchors.yes-a-html.hreflang">request</a> again (sometimes this helps).</li> - <li><span id="contact-support-if-y-3404332025" data-replace-self="path:text.contact-support-if-y-3404332025.text">Contact support if you continue to have problems.</span></li> + <li>Contact support if you continue to have problems.</li> - <li><span id="if-you-received-this-423958995" data-replace-self="path:text.if-you-received-this-423958995.text">If you received this message as a result of a request to the server API, then check the structure + <li>If you received this message as a result of a request to the server API, then check the structure against the documentation. - </span></li> + </li> </ul> - <p><span id="you-can-try-the-foll-3363859033" data-replace-self="path:text.you-can-try-the-foll-3363859033.text">You can try the following steps:</span></p> + <p>You can try the following steps:</p> </div> </div> @@ -68,5 +68,25 @@ data-attributes="href path:anchors.yes-a-html.href,title path:anchors.yes-a-html </monster-state> +<monster-datatable id="datatable-order-list" data-monster-datasource-selector="#orderListDatasource"> + <template id="datatable-order-list-row"> + <div data-monster-head="OID" data-monster-mode="fixed" data-monster-sortable="oid" data-monster-grid-template="0.5fr"><a data-monster-attributes="href path:datatable-order-list-row.oid | tostring | prefix:/app/commerce/order-detail?oid=" data-monster-replace="path:datatable-order-list-row.oid"></a></div> + <div data-monster-head="date" data-monster-sortable="orderDate" data-monster-replace="path:datatable-order-list-row.orderDate | datetimeformat"></div> + <div data-monster-head="username" data-monster-mode="hidden" data-monster-replace="path:datatable-order-list-row.userName"></div> + <div data-monster-head="customer">, </div> + <div data-monster-head="zipcode" data-monster-replace="path:datatable-order-list-row.deliveryAddressZipcode"></div> + <div data-monster-head="city" data-monster-replace="path:datatable-order-list-row.deliveryAddressCity"></div> + <div data-monster-head="country" data-monster-replace="path:datatable-order-list-row.deliveryAddressCountry | prefix:country_ | i18n"></div> + <div data-monster-head="street" data-monster-mode="hidden" data-monster-replace="path:datatable-order-list-row.deliveryAddressAddress1"></div> + <div data-monster-head="order state" data-monster-align="end"></div> + <div data-monster-head="workflow state" data-monster-align="end" data-monster-replace="path:datatable-order-list-row.workflowState"></div> + <div data-monster-head="total" data-monster-align="end"></div> + <div data-monster-head="company" data-monster-grid-template="0.8fr" data-monster-replace="path:datatable-order-list-row.companySHID | tostring | prefix:<img src='/alvine/upload/company/files/ | suffix:/shopicon.gif'>"></div> + <div data-monster-head="channel order number" data-monster-replace="path:datatable-order-list-row.channelOrderID"></div> + <div data-monster-head="resubmissionDate" data-monster-mode="hidden"></div> + <div data-monster-head="payment type" data-monster-replace="path:datatable-order-list-row.localStrings.paymentType"></div> + </template> +</monster-datatable> + </body></html> \ No newline at end of file diff --git a/devenv.lock b/devenv.lock index fd801585765d26030f6ff1bc49ccbbad2cc41806..8e0ea4a87f9db4169a6f615bc542f595024b7c66 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,11 +3,11 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1689667485, - "narHash": "sha256-tLNoMRSPLlW1D4wgNpSIPUUHd6x1GdjAhETLpRTKnfo=", + "lastModified": 1690413082, + "narHash": "sha256-CPR3WcnrrIiDZJiMo4RlyZB0M3576pHmtlTUnMUTugA=", "owner": "cachix", "repo": "devenv", - "rev": "6f8add968bc12bf81d845eb7dc684a0733bb1518", + "rev": "148c4a21e50428728e97f3cdf59166b6007db8a7", "type": "github" }, "original": { @@ -74,11 +74,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1689631193, - "narHash": "sha256-AGSkBZaiTODQc8eT1rZDrQIjtb8JtFwJ0wVPzArlrnM=", + "lastModified": 1690441914, + "narHash": "sha256-Ac+kJQ5z9MDAMyzSc0i0zJDx2i3qi9NjlW5Lz285G/I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "57695599bdc4f7bfe5d28cfa23f14b3d8bdf8a5f", + "rev": "db8672b8d0a2593c2405aed0c1dfa64b2a2f428f", "type": "github" }, "original": { @@ -115,11 +115,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1689668210, - "narHash": "sha256-XAATwDkaUxH958yXLs1lcEOmU6pSEIkatY3qjqk8X0E=", + "lastModified": 1690464206, + "narHash": "sha256-38V4kmOh6ikpfGiAS+Kt2H/TA2DubSqE66veP/jmB4Q=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "eb433bff05b285258be76513add6f6c57b441775", + "rev": "9289996dcac62fd45836db7c07b87d2521eb526d", "type": "github" }, "original": {