{"id":18026784,"url":"https://github.com/yegor256/xembly","last_synced_at":"2025-05-15T05:06:56.471Z","repository":{"id":9814199,"uuid":"11796850","full_name":"yegor256/xembly","owner":"yegor256","description":"Assembly for XML: an imperative language for creating and modifying XML documents (and a Java library)","archived":false,"fork":false,"pushed_at":"2025-05-05T03:59:14.000Z","size":5602,"stargazers_count":242,"open_issues_count":14,"forks_count":30,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-05-05T04:34:06.839Z","etag":null,"topics":["java","xml","xml-builder","xml-documents","xml-editor","xpath"],"latest_commit_sha":null,"homepage":"https://www.xembly.org","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yegor256.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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,"zenodo":null}},"created_at":"2013-07-31T17:34:53.000Z","updated_at":"2025-05-05T03:57:49.000Z","dependencies_parsed_at":"2023-11-07T09:28:18.857Z","dependency_job_id":"71d7a4f3-3052-405c-9b5a-1694788f7076","html_url":"https://github.com/yegor256/xembly","commit_stats":{"total_commits":595,"total_committers":18,"mean_commits":33.05555555555556,"dds":0.5865546218487395,"last_synced_commit":"35067a4e2cd712b9393df00f43686f485f5f767e"},"previous_names":[],"tags_count":61,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yegor256%2Fxembly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yegor256%2Fxembly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yegor256%2Fxembly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yegor256%2Fxembly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yegor256","download_url":"https://codeload.github.com/yegor256/xembly/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254276447,"owners_count":22043867,"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":["java","xml","xml-builder","xml-documents","xml-editor","xpath"],"created_at":"2024-10-30T08:08:02.969Z","updated_at":"2025-05-15T05:06:51.462Z","avatar_url":"https://github.com/yegor256.png","language":"Java","readme":"# XML Modifying Imperative Language (and Java Lib)\n\n[![EO principles respected here](https://www.elegantobjects.org/badge.svg)](https://www.elegantobjects.org)\n[![DevOps By Rultor.com](https://www.rultor.com/b/yegor256/xembly)](https://www.rultor.com/p/yegor256/xembly)\n[![We recommend IntelliJ IDEA](https://www.elegantobjects.org/intellij-idea.svg)](https://www.jetbrains.com/idea/)\n\n[![mvn](https://github.com/yegor256/xembly/actions/workflows/mvn.yml/badge.svg)](https://github.com/yegor256/xembly/actions/workflows/mvn.yml)\n[![PDD status](https://www.0pdd.com/svg?name=yegor256/xembly)](https://www.0pdd.com/p?name=yegor256/xembly)\n[![codecov](https://codecov.io/gh/yegor256/xembly/branch/master/graph/badge.svg)](https://codecov.io/gh/yegor256/xembly)\n[![codebeat badge](https://codebeat.co/badges/c07bdf31-182b-4e4d-a25e-df405c1d877d)](https://codebeat.co/projects/github-com-yegor256-xembly)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/f244032e289f4fc2a8d9db8c84251490)](https://www.codacy.com/gh/yegor256/xembly/dashboard)\n[![Javadoc](https://www.javadoc.io/badge/com.jcabi.incubator/xembly.svg)](https://www.javadoc.io/doc/com.jcabi.incubator/xembly)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.jcabi.incubator/xembly/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.jcabi.incubator/xembly)\n![Hits-of-Code](https://raw.githubusercontent.com/yegor256/xembly/gh-pages/hoc-badge.svg)\n![Lines-of-Code](https://raw.githubusercontent.com/yegor256/xembly/gh-pages/loc-badge.svg)\n[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=yegor256_xembly\u0026metric=code_smells)](https://sonarcloud.io/summary/new_code?id=yegor256_xembly)\n\n**Xembly** is an\n[Assembly](http://en.wikipedia.org/wiki/Assembly_language)-like\n[imperative](http://en.wikipedia.org/wiki/Imperative_programming)\nprogramming language\nfor data manipulation in XML documents.\nIt is a much simplier alternative to\n[DOM](https://en.wikipedia.org/wiki/Document_Object_Model),\n[XSLT](http://www.w3.org/TR/xslt), and [XQuery](http://www.w3.org/TR/xquery).\nRead this blog post\nfor a more detailed explanation: [Xembly, an Assembly for XML][blog].\nYou may also want to watch\n[this webinar](https://www.youtube.com/watch?v=oNtTAF0UjjA).\n\nYou need this dependency:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.jcabi.incubator\u003c/groupId\u003e\n  \u003cartifactId\u003exembly\u003c/artifactId\u003e\n  \u003cversion\u003e0.32.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nHere is a command line implementation (as Ruby gem):\n[xembly-gem](https://github.com/yegor256/xembly-gem)\n\nFor example, you have an XML document:\n\n```xml\n\u003corders\u003e\n  \u003corder id=\"553\"\u003e\n    \u003camount\u003e$45.00\u003c/amount\u003e\n  \u003c/order\u003e\n\u003c/orders\u003e\n```\n\nThen, you want to change the amount of the order #553\nfrom `$45.00` to `$140.00`. Xembly script would look like this:\n\n```text\nXPATH \"orders/order[@id=553]\";\nXPATH \"amount\";\nSET \"$140.00\";\n```\n\nAs you see, it's much simpler and compact than\n[DOM](https://en.wikipedia.org/wiki/Document_Object_Model),\n[XSLT](http://www.w3.org/TR/xslt),\nor [XQuery](http://www.w3.org/TR/xquery).\n\nThis Java package implements Xembly:\n\n```java\nDocument document = DocumentBuilderFactory.newInstance()\n  .newDocumentBuilder().newDocument();\nnew Xembler(\n  new Directives(\n    \"ADD 'orders'; ADD 'order'; ATTR 'id', '553'; SET '$140.00';\"\n  )\n).apply(document);\n```\n\nSince version 0.9 you can directly transform directives to XML:\n\n```java\nString xml = new Xembler(\n  new Directives()\n    .add(\"root\")\n    .add(\"order\")\n    .attr(\"id\", \"553\")\n    .set(\"$140.00\")\n).xml();\n```\n\nThis code will produce the following XML document:\n\n```xml\n\u003croot\u003e\n  \u003corder id=\"553\"\u003e$140\u003c/order\u003e\n\u003c/root\u003e\n```\n\n## Directives\n\nThis is a full list of supported directives, in the current version:\n\n* `ADD`: adds new node to all current nodes\n* `ADDIF`: adds new node, if it's absent\n* `ATTR`: sets new attribute to current nodes\n* `SET`: sets text value of current node\n* `XSET`: sets text value, calculating it with XPath\n* `XATTR`: sets attribute value, calculating it with XPath\n* `CDATA`: same as `SET`, but makes `CDATA`\n* `UP`: moves cursor one node up\n* `XPATH`: moves cursor to the nodes found by XPath\n* `REMOVE`: removes all current nodes\n* `STRICT`: throws an exception if cursor is missing nodes\n* `PI`: adds processing instruction\n* `PUSH`: saves cursor in stack\n* `POP`: retrieves cursor from stack\n* `NS`: sets namespace of all current nodes\n* `COMMENT`: adds XML comment\n\nThe \"cursor\" or \"current nodes\" is where we're currently located\nin the XML document. When Xembly script starts, the cursor is\nempty: it simply points to the highest level in the XML hierarchy.\nPay attention, it doesn't point to the root node. It points to one\nlevel above the root. Remember, when a document is empty, there is no root node.\n\nThen, we start executing directives one by one. After each directive\nthe cursor is moving somewhere. There may be many nodes under the cursor,\nor just one, or none. For example, let's assume we're starting\nwith this simple document `\u003ccar/\u003e`:\n\n```text\nADD 'hello';        // Nothing happens, since the cursor is empty\nXPATH '/car';       // There is one node \u003ccar\u003e under the cursor\nADD 'make';         // The result is \"\u003ccar\u003e\u003cmake/\u003e\u003c/car\u003e\",\n                    // the cursor has one node \"\u003cmake/\u003e\"\nATTR 'name', 'BMW'; // The result is \"\u003ccar\u003e\u003cmake name='BMW'/\u003e\u003c/car\u003e\",\n                    // the cursor still points to one node \"\u003cmake/\u003e\"\nUP;                 // The cursor has one node \"\u003ccar\u003e\"\nADD 'mileage';      // The result is \"\u003ccar\u003e\u003cmake name='BMW'/\u003e\u003cmileage/\u003e\u003c/car\u003e\",\n                    // the cursor still has one node \"\u003ccar\u003e\"\nXPATH '*';          // The cursor has two nodes \"\u003cmake name='BMW'/\u003e\"\n                    // and \"\u003cmileage/\u003e\"\nREMOVE;             // The result is \"\u003ccar/\u003e\", since all nodes under\n                    // the cursor are removed\n```\n\nYou can create a collection of directives either from a text or\nvia supplementary methods, one per each directive. In both cases,\nyou need to use the `Directives` class:\n\n```java\nimport org.xembly.Directives;\nnew Directives(\"XPATH '//car'; REMOVE;\");\nnew Directives().xpath(\"//car\").remove();\n```\n\nThe second option is preferable, because it is faster — there is\nno parsing involved.\n\n### ADD\n\nThe `ADD` directive adds a new node to every node in the current node set.\n`ADD` expects exactly one mandatory argument, which is the name of\na new node to be added (case sensitive):\n\n```text\nADD 'orders';\nADD 'order';\n```\n\nEven if a node with the same name already exists, a new node\nwill be added. Use `ADDIF` if you need to add only if the same-name node\nis absent.\n\nAfter the execution, the `ADD` directive moves the cursor\nto the nodes just added.\n\n### ADDIF\n\nThe `ADDIF` directive adds a new node to every node of the current set,\nonly if it is absent. `ADDIF` expects exactly one argument, which\nis the name of the node to be added (case sensitive):\n\n```text\nADD 'orders';\nADDIF 'order';\n```\n\nAfter the execution, the `ADDIF` directive moves the cursor\nto the nodes just added.\n\n### ATTR\n\nThe `ATTR` directive sets an attribute to every node of the current set.\n`ATTR` expects exactly two arguments, where the first is the name\nof the attribute and the second is the value to set:\n\n```text\nADD 'order';\nATTR 'price', '$49.99';\n```\n\nAfter the execution, `ATTR` doesn't move the cursor.\n\nIf it's necessary to make sure the attribute belongs to a certain\nnamespace, put the namespace and its prefix into the\nattribute name separating them with spaces:\n\n```text\nADD 'flower';\nATTR 'noNamespaceSchemaLocation xsi http://www.w3.org/2001/XMLSchema-instance', 'foo.xsd';\n```\n\nThis will generate the following document:\n\n```xml\n\u003cflower xsi:noNamespaceSchemaLocation=\"foo.xsd\"/\u003e\n```\n\n### SET\n\nThe `SET` directive changes text content of all current nodes, and expects\nexactly one argument, which is the text content to set:\n\n```text\nADD \"employee\";\nSET \"John Smith\";\n```\n\n`SET` doesn't move the cursor anywhere.\n\n### XSET\n\nThe `XSET` directive changes text content of all current nodes to a value\ncalculated with the provided XPath expression:\n\n```text\nADD \"product-1\";\nADD \"price\";\nXSET \"sum(/products/price) div count(/products)\";\n```\n\n`XSET` doesn't move the cursor anywhere.\n\n### XATTR\n\nThe `XATTR` directive changes the value of an attribute of\nall current nodes to a value\ncalculated with the provided XPath expression:\n\n```text\nADD \"product-1\";\nADD \"price\";\nXATTR \"s\", \"sum(/products/price) div count(/products)\";\n```\n\n`XATTR` doesn't move the cursor anywhere.\n\n### UP\n\nThe `UP` directive moves all current nodes to their parents.\n\n### XPATH\n\nThe `XPATH` directive re-points the cursor to the nodes found\nby the provided XPath expression:\n\n```text\nXPATH \"//employee[@id='234' and name='John Smith']/name\";\nSET \"John R. Smith\";\n```\n\n### REMOVE\n\nThe `REMOVE` directive removes current nodes under the cursor and\nmoves the cursor to their parents:\n\n```text\nADD \"employee\";\nREMOVE;\n```\n\n### STRICT\n\nThe `STRICT` directive checks that there is a certain number of current nodes:\n\n```text\nXPATH \"//employee[name='John Doe']\";  // Move the cursor to the employee\nSTRICT \"1\";                           // Throw an exception if there\n                                      // is not exactly one node under\n                                      // the cursor\n```\n\nThis is a very effective mechanism of validation of your script,\nin production mode. It is similar to `assert`  statement in Java.\nIt is recommended to use `STRICT` regularly, to make sure your\ncursor has correct amount of nodes, to avoid unexpected modifications.\n\n`STRICT` doesn't move the cursor anywhere.\n\n### PI\n\nThe `PI` directive adds a new processing directive to the XML:\n\n```text\nPI \"xsl-stylesheet\" \"href='http://example.com'\";\n```\n\n`PI` doesn't move the cursor anywhere.\n\n### PUSH and POP\n\nThe `PUSH` and `POP` directives save current DOM position to stack\nand restore it from there.\n\nLet's say, you start your Xembly manipulations from a place in DOM,\nwhich location is not determined for you. After your manipulations are\ndone, you want to get back to exactly the same place. You should\nuse `PUSH` to save your current location and `POP` to restore it\nback, when manipulations are finished, for example:\n\n```assemlby\nPUSH;                        // Doesn't matter where we are\n                             // We just save the location to stack\nXPATH '//user[@id=\"123\"]';   // Move the cursor to a completely\n                             // different location in the XML\nADD 'name';                  // Add \"\u003cname/\u003e\" to all nodes under the cursor\nSET 'Jeff';                  // Set text value to the nodes\nPOP;                         // Get back to where we were before the PUSH\n```\n\n`PUSH` basically saves the cursor into stack and `POP` restores it from there.\nThis is a very similar technique to `PUSH`/`POP` directives in Assembly. The\nstack has no limits, you can push multiple times and pop them back. It is\na stack, that's why it is First-In-Last-Out (FILO).\n\nThis operation is fast and it is highly recommended to use it everywhere,\nto be sure you're not making unexpected changes to the XML document.\n\n### NS\n\nThe `NS` directive adds a namespace attribute to a node:\n\n```text\nXPATH '/garage/car';                // Move the cursor to \"\u003ccar/\u003e\" node(s)\nNS \"http://www.w3.org/TR/html4/\";   // Set the namespace over there\n```\n\nIf an original document was like this:\n\n```xml\n\u003cgarage\u003e\n  \u003ccar\u003eBMW\u003c/car\u003e\n  \u003ccar\u003eToyota\u003c/car\u003e\n\u003c/garage\u003e\n```\n\nAfter the applying of that two directives, it will look like this:\n\n```xml\n\u003cgarage xmlns:a=\"http://www.w3.org/TR/html4/\"\u003e\n  \u003ca:car\u003eBMW\u003c/a:car\u003e\n  \u003ca:car\u003eToyota\u003c/a:car\u003e\n\u003c/garage\u003e\n```\n\nThe namspace prefix may not necessarily be `a:`.\n\n`NS` doesn't move the cursor anywhere.\n\n## XML Collections\n\nLet's say you want to build an XML document with a collection of names:\n\n```java\npackage org.xembly.example;\nimport org.xembly.Directives;\nimport org.xembly.Xembler;\npublic class XemblyExample {\n  public static void main(String[] args) throws Exception {\n    String[] names = new String[] {\n      \"Jeffrey Lebowski\",\n      \"Walter Sobchak\",\n      \"Theodore Donald 'Donny' Kerabatsos\",\n    };\n    Directives directives = new Directives().add(\"actors\");\n    for (String name : names) {\n      directives.add(\"actor\").set(name).up();\n    }\n    System.out.println(new Xembler(directives).xml());\n  }\n}\n```\n\nThe standard output will contain this text:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cactors\u003e\n  \u003cactor\u003eJeffrey Lebowski\u003c/actor\u003e\n  \u003cactor\u003eWalter Sobchak\u003c/actor\u003e\n  \u003cactor\u003eTheodore Donald \u0026apos;Donny\u0026apos; Kerabatsos\u003c/actor\u003e\n\u003c/actors\u003e\n```\n\n## Merging Documents\n\nWhen you need to add an entire XML document, you can convert\nit first into Xembly directives and then add them all together:\n\n```java\nIterable\u003cIterable\u003e dirs = new Directives()\n  .add(\"garage\")\n  .append(Directives.copyOf(node))\n  .add(\"something-else\");\n```\n\nThis static utility method `copyOf()` converts an instance of class\n`org.w3c.dom.Node` into a collection of Xembly directives. Then,\nthe `append()` method adds them all together to the main list.\n\nUnfortunately, not every valid XML document can be parsed by `copyOf()`. For\nexample, this one will lead to a runtime exception:\n`\u003ccar\u003e2015\u003cname\u003eBMW\u003c/name\u003e\u003c/car\u003e`. Read more about Xembly limitations,\na few paragraphs below.\n\n## Escaping Invalid XML Text\n\nXML, as a standard, doesn't allow certain characters in its body. For example,\nthis code will throw an exception:\n\n```java\nString xml = new Xembler(\n  new Directives().add(\"car\").set(\"\\u00\")\n).xml();\n```\n\nThe character `\\u00` is not allowed in XML. Actually, these ranges\nare also not allowed: `\\u00..\\u08`, `\\u0B..\\u0C`, `\\u0E..\\u1F`,\n`\\u7F..\\u84`, and `\\u86..u9F`.\n\nThis means that you should validate everything and make sure you're\nsetting only the \"valid\" text values to your XML nodes. Sometimes,\nit's not feasible\nto always check them. Sometimes you may simply need to save whatever\nis possible and call it a day. There a utility static method\n`Xembler.escape()`, to help\nyou do that:\n\n```java\nString xml = new Xembler(\n  new Directives().add(\"car\").set(Xembler.escape(\"\\u00\"))\n).xml();\n```\n\nThis code won't throw an exception. The `Xembler.escape()` method will\nconvert \"\\u00\" to \"\\\\u0000\". It is recommended to use this method\neverywhere, if you are not sure about the quality of the content.\n\n## Shaded Xembly JAR With Dependencies\n\nUsually, you're supposed to use this dependency in your `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.jcabi.incubator\u003c/groupId\u003e\n  \u003cartifactId\u003exembly\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n\nHowever, if you have conflicts between dependencies, you can\nuse our \"shaded\" JAR, that includes all dependencies:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.jcabi.incubator\u003c/groupId\u003e\n  \u003cartifactId\u003exembly\u003c/artifactId\u003e\n  \u003cclassifier\u003ejar-with-dependencies\u003c/classifier\u003e\n\u003c/dependency\u003e\n```\n\n## Known Limitations\n\nXembly is not intended to be a replacement of XSL or XQuery. It is\na lightweight (!) instrument for XML manipulations. There are a few things\nthat can't be done by means of Xembly:\n\n* DTD section can't be modified\n\n* Elements and text content can't be mixed, e.g.\nthis structure is not supported: `\u003ctest\u003ehello \u003cb\u003efriend\u003c/a\u003e\u003c/test\u003e`\n\nSome of these limitations may be removed in the next versions. Please,\nsubmit [an issue](https://github.com/yegor256/xembly/issues).\n\n## How To Contribute\n\nFork repository, make changes, send us a pull request. We will review\nyour changes and apply them to the `master` branch shortly, provided\nthey don't violate our quality standards. To avoid frustration, before\nsending us your pull request, please run full Maven build:\n\n```bash\nmvn clean install -Pqulice\n```\n\nYou must fix all static analysis issues, otherwise we won't be able\nto merge your pull request. The build must be \"clean\".\n\n## Delivery Pipeline\n\nGit `master` branch is our cutting edge of development. It always contains\nthe latest version of the product, always in `-SNAPSHOT` suffixed version.\nNobody\nis allowed to commit directly to `master` \u0026mdash; this branch is basically\n[read-only](http://www.yegor256.com/2014/07/21/read-only-master-branch.html).\nEverybody contributes changes via\n[pull requrests](http://www.yegor256.com/2014/04/15/github-guidelines.html).\nWe are\nusing [rultor](https://www.rultor.com), a hosted\n[chatbot][blog-chatbots],\nin order to merge pull requests into `master`.\nOnly our architect is allowed to send pull\nrequests to @rultor for merge, using `merge` command.\nBefore it happens, a mandatory code review must be performed for a pull request.\n\nAfter each successful merge of a pull request, our project manager\ngives `deploy` command to @rultor. The code from `master` branch is\ntested, packaged, and deployed to [Sonatype](http://central.sonatype.org/),\nin version `*-SNAPSHOT`.\n\nEvery once in a while, the architect may decide that it's time to release\na new [minor/major](http://www.semver.org) version of the product. When\nit happens, he gives `release` command to @rultor. The code from `master`\nbranch is tested, versioned, packaged, and deployed to\n[Sonatype](http://central.sonatype.org/) and\n[Maven Central](http://search.maven.org/).\nA new Git tag is created. A new GitHub release is created\nand briefly documented.\nAll this is done automatically by @rultor.\n\n## Got questions?\n\nIf you have questions or general suggestions, don't hesitate to submit\na new [Github issue](https://github.com/yegor256/xembly/issues/new).\nBut keep these\n[Five Principles of Bug Tracking][blog-bugs]\nin mind.\n\n[blog]: http://www.yegor256.com/2014/04/09/xembly-intro.html\n[blog-bugs]: http://www.yegor256.com/2014/11/24/principles-of-bug-tracking.html\n[blog-chatbots]: http://www.yegor256.com/2015/11/03/chatbot-better-than-ui-for-microservice.html\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyegor256%2Fxembly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyegor256%2Fxembly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyegor256%2Fxembly/lists"}