{"id":17723545,"url":"https://github.com/doriantaylor/rb-xml-mixup","last_synced_at":"2025-03-31T13:41:45.742Z","repository":{"id":56898816,"uuid":"129149504","full_name":"doriantaylor/rb-xml-mixup","owner":"doriantaylor","description":"XML::Mixup: A mixin for XML markup","archived":false,"fork":false,"pushed_at":"2023-11-23T01:26:45.000Z","size":44,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-23T20:54:15.937Z","etag":null,"topics":["mixin","mixins","rdfa","xhtml","xml","xml-serialization"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/doriantaylor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2018-04-11T20:21:17.000Z","updated_at":"2022-06-01T04:39:55.000Z","dependencies_parsed_at":"2024-10-26T02:26:19.230Z","dependency_job_id":null,"html_url":"https://github.com/doriantaylor/rb-xml-mixup","commit_stats":{"total_commits":32,"total_committers":2,"mean_commits":16.0,"dds":0.0625,"last_synced_commit":"10da8f7105f7344b32375c28457051db3567add3"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Frb-xml-mixup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Frb-xml-mixup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Frb-xml-mixup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Frb-xml-mixup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doriantaylor","download_url":"https://codeload.github.com/doriantaylor/rb-xml-mixup/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246475861,"owners_count":20783740,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["mixin","mixins","rdfa","xhtml","xml","xml-serialization"],"created_at":"2024-10-25T15:43:19.905Z","updated_at":"2025-03-31T13:41:45.713Z","avatar_url":"https://github.com/doriantaylor.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# XML::Mixup: A mixin for XML markup\n\n```ruby\nrequire 'xml-mixup'\n\nclass Anything\n  include XML::Mixup\nend\n\nsomething = Anything.new\n\n# generate a structure\nnode = something.markup spec: [\n  { '#pi'   =\u003e 'xml-stylesheet', type: 'text/xsl', href: '/transform' },\n  { '#dtd'  =\u003e :html },\n  { '#html' =\u003e [\n    { '#head' =\u003e [\n      { '#title' =\u003e 'look ma, title' },\n      { '#elem'  =\u003e :base, href: 'http://the.base/url' },\n    ] },\n    { '#body' =\u003e [\n      { '#h1' =\u003e 'Illustrious Heading' },\n      { '#p'  =\u003e :lolwut },\n    ] },\n  ], xmlns: 'http://www.w3.org/1999/xhtml' }\n]\n\n# `node` will correspond to the last thing generated. In this\n# case, it will be a text node containing 'lolwut'.\n\ndoc = node.document\nputs doc.to_xml\n# =\u003e \u003c?xml version=\"1.0\"?\u003e\n# =\u003e \u003c?xml-stylesheet href=\"/transform\" type=\"text/xsl\"?\u003e\n# =\u003e \u003c!DOCTYPE html\u003e\n# =\u003e \u003chtml xmlns=\"http://www.w3.org/1999/xhtml\"\u003e\n# =\u003e   \u003chead\u003e\n# =\u003e     \u003ctitle\u003elook ma, title\u003c/title\u003e\n# =\u003e     \u003cbase href=\"http://the.base/url\"/\u003e\n# =\u003e   \u003c/head\u003e\n# =\u003e   \u003cbody\u003e\n# =\u003e     \u003ch1\u003eIllustrious Heading\u003c/h1\u003e\n# =\u003e     \u003cp\u003elolwut\u003c/p\u003e\n# =\u003e   \u003c/body\u003e\n# =\u003e \u003c/html\u003e\n```\n\n## Yet another XML markup generator?\n\nSome time ago, [I](https://doriantaylor.com/) wrote a Perl module called\n[Role::Markup::XML](https://metacpan.org/pod/Role::Markup::XML). I did this\nbecause I had a lot of XML to generate, and was dissatisfied with what\nwas currently on offer. Now I have a lot of XML to generate using\nRuby, and found a lot of the same things:\n\n### Structure is generated by procedure calls\n\nGranted it's a lot nicer to do this sort of thing in Ruby, but at the\nend of the day, the thing generating the XML is a nested list of\nmethod calls — _not_ a declarative data structure.\n\n### Document has to be generated all in one shot\n\nIt's not super-easy to generate a piece of the target document and\nthen go back and generate some more (although\n`Nokogiri::XML::Builder.with` is a nice start). This plus the last\npoint leads to all sorts of cockamamy constructs which are almost as\nlife-sucking as writing raw DOM routines.\n\n### Hard to do surgery on existing documents\n\nThis comes up a lot: you have an existing document and you want to add\neven just a single node to it — say, in between two nodes just for\nfun. Good luck with that.\n\n### Enter `XML::Mixup`\n\n* __The input consists of ordinary Ruby data objects__ so you can\n  build them up ahead of time, in bulk, transform them using familiar\n  operations, etc.,\n* __Sprinkle pre-built XML subtrees anywhere into the spec__ so you\n  can memoize repeating elements, or otherwise compile a document\n  incrementally,\n* __Attach new generated content anywhere:__ underneath a parent node,\n  or before, after, or _instead of_ a node at the sibling level.\n\n## The tree spec format\n\nAt the heart of this module is a single method called `markup`, which,\namong other things, takes a `:spec`. The spec can be any composite of\nthese objects, and will behave as described:\n\n### Hashes\n\nThe principal construct in `XML::Mixup` is the `Hash`. You can\ngenerate pretty much any node with it:\n\n#### Elements\n\n```ruby\n{ '#tag' =\u003e 'foo' }                 # =\u003e \u003cfoo/\u003e\n\n# or, with the element name as a symbol\n{ '#element' =\u003e :foo }              # =\u003e \u003cfoo/\u003e\n\n# or\n{ '#elem' =\u003e 'foo' }                # =\u003e \u003cfoo/\u003e\n\n# or, with nil as a key\n{ nil =\u003e :foo }                     # =\u003e \u003cfoo/\u003e\n\n# or, with attributes\n{ nil =\u003e :foo, bar: :hi }           # =\u003e \u003cfoo bar=\"hi\"/\u003e\n\n# or, with namespaces\n{ nil =\u003e :foo, xmlns: 'urn:x-bar' } # =\u003e \u003cfoo xmlns=\"urn:x-bar\"/\u003e\n\n# or, with more namespaces\n{ nil =\u003e :foo, xmlns: 'urn:x-bar', 'xmlns:hurr' =\u003e 'urn:x-durr' }\n# =\u003e \u003cfoo xmlns=\"urn:x-bar\" xmlns:hurr=\"urn:x-durr\"/\u003e\n\n# or, with content\n{ nil =\u003e [:foo, :hi] }              # =\u003e \u003cfoo\u003ehi\u003c/foo\u003e\n\n# or, shove your child nodes into an otherwise content-less key\n{ [:hi] =\u003e :foo, bar: :hurr }       # =\u003e \u003cfoo bar=\"hurr\"\u003ehi\u003c/foo\u003e\n\n# or, if you have content and the element name is not a reserved word\n{ '#html' =\u003e { '#head' =\u003e { '#title' =\u003e :hi } } }\n# =\u003e \u003chtml\u003e\u003chead\u003e\u003ctitle\u003ehi\u003c/title\u003e\u003c/head\u003e\u003c/html\u003e\n\n# also works with namespaces\n{ '#atom:feed' =\u003e nil, 'xmlns:atom' =\u003e 'http://www.w3.org/2005/Atom' }\n# =\u003e \u003catom:feed xmlns:atom=\"http://www.w3.org/2005/Atom\"/\u003e\n```\n\nReserved hash keywords are: `#comment`, `#cdata`, `#doctype`, `#dtd`,\n`#elem`, `#element`, `#pi`, `#processing-instruction`, `#tag`. Note\nthat the constructs `{ nil =\u003e :foo }`, `{ nil =\u003e 'foo' }`, and `{\n'#foo' =\u003e nil }`, plus `[]` anywhere you see `nil`, are all\nequivalent.\n\nAttributes are sorted lexically. Composite attribute values get\nflattened like this:\n\n```ruby\n{ nil =\u003e :foo, array: [:a, :b], hash: { e: :f, c: :d } }\n# =\u003e \u003cfoo array=\"a b\" hash=\"c: d e: f\"/\u003e\n```\n\nNote that attribute values can also be a `Proc`, which are fed\narbitrary arguments from the `markup` method. The `Proc` is expected\nto return something which can subsequently flattened. If an attribute\nvalue is `nil` or ultimately resolves to `nil`, or an empty `Array` or\n`Hash`, that attribute will be omitted. `nil` values in arrays or\nhashes will also be skipped, as will empty-string values in\narrays. This is different behaviour from versions prior to 0.1.10,\nwhere `nil` (or, e.g., `[]`) would produce an attribute containing the\nempty string.\n\nThis change was made to eliminate a lot of clunky logic in application\ncode to determine whether or not to include a given attribute. If you\nneed to render attributes explicitly with empty strings, then\nexplicitly pass in the empty string.\n\n#### Processing instructions\n\n```ruby\n{ '#pi' =\u003e 'xml-stylesheet', type: 'text/xsl', href: '/transform' }\n# =\u003e \u003c?xml-stylesheet type=\"text/xsl\" href=\"/transform\"?\u003e\n\n# or, if you like typing\n{ '#processing-instruction' =\u003e :hurr } # =\u003e \u003c?hurr?\u003e\n```\n\n#### `DOCTYPE` declarations\n\n```ruby\n{ '#dtd' =\u003e :html } # =\u003e \u003c!DOCTYPE html\u003e\n\n# or (note either :public or :system can be nil)\n{ '#dtd' =\u003e [:html, :public, :system] }\n# =\u003e \u003c!DOCTYPE html PUBLIC \"public\" SYSTEM \"system\"\u003e\n\n# or, same thing\n{ '#doctype' =\u003e :html, public: :public, system: :system }\n```\n\n#### Comments and `CDATA` sections\n\nComments and `CDATA` are flattened into string literals:\n\n```ruby\n{ '#comment' =\u003e :whatever }     # =\u003e \u003c!-- whatever --\u003e\n\n{ '#cdata' =\u003e '\u003cwhat-everrr\u003e' } # =\u003e \u003c![CDATA[\u003cwhat-everrr\u003e]]\u003e\n```\n\nPretty straight forward?\n\n### Arrays\n\nParts of a spec that are arrays (or really anything that can be turned\ninto one) are attached at the same level of the document in the\nsequence given, as you might expect.\n\n### `Nokogiri::XML::Node` objects\n\nThese are automatically cloned, but otherwise passed in as-is.\n\n### `Proc`s, lambdas etc.\n\nThese are executed with any supplied `:args`, and then `markup` is run\nagain over the result. (Take care not to supply a `Proc` that produces\nanother `Proc`.)\n\n### Everything else\n\nTurned into a text node.\n\n## Documentation\n\nGenerated and deposited\n[in the usual place](http://www.rubydoc.info/gems/xml-mixup/).\n\n## Installation\n\nCome on, you know how to do this:\n\n    $ gem install xml-mixup\n\nOr, [download it off rubygems.org](https://rubygems.org/gems/xml-mixup).\n\n## Contributing\n\nBug reports and pull requests are welcome at\n[the GitHub repository](https://github.com/doriantaylor/rb-xml-mixup).\n\n## The Future\n\nAs mentioned, this is pretty much a straight-across port\nof [Role::Markup::XML](https://metacpan.org/pod/Role::Markup::XML),\nwhere it makes sense in Perl to bolt a bunch of related pseudo-private\n`_FOO`-looking instance methods onto an object so you can use them to\nmake more streamlined methods. This may or may not make the same kind\nof sense with Ruby.\n\nIn particular, these methods do not touch the calling object's\nstate. In fact they _should_ be completely stateless and side-effect\nfree. Likewise, they are really meant to be private. As such, it may\nmake sense to simply bundle them as class methods and use them as\nsuch. I don't know, I haven't decided yet.\n\n## License\n\nThis software is provided under\nthe [Apache License, 2.0](https://www.apache.org/licenses/LICENSE-2.0).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoriantaylor%2Frb-xml-mixup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoriantaylor%2Frb-xml-mixup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoriantaylor%2Frb-xml-mixup/lists"}