diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..84e8ce4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+mfenced-min.js
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bcb3b9a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 Igalia S.L.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b6f236d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+# Copyright (c) 2016 Igalia S.L.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+CLOSURE_COMPILER=java -jar ~/node_modules/google-closure-compiler/compiler.jar
+
+mfenced-min.js: mfenced.js
+ $(CLOSURE_COMPILER) --compilation_level ADVANCED_OPTIMIZATIONS < $< > $@
+
+clean:
+ rm -f mfenced-min.js
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f34d060
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# mfenced polyfill
+
+This repository contains a small polyfill for the MathML
+[mfenced element](https://www.w3.org/TR/MathML/chapter3.html#presm.fenced).
+In order to use it, just load the mfenced-min.js script:
+
+
+
+ ...
+
+ ...
+
+ ...
+
+
+At page load, all the mfenced elements will be converted into their equivalent
+expanded form. A function window.expandMathMLFencedElements is also defined and
+can be used to execute this conversion again later.
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..7a41b16
--- /dev/null
+++ b/index.html
@@ -0,0 +1,197 @@
+
+
+
+ The mfenced element
+
+
+
+
+
The mfenced element
+
+
This page contains some tests for the mfenced element. Click the following button to load a polyfill and convert them into an equivalent expanded form.
+
+
+
+
+
empty:
+
+
+
+
1 argument:
+
+
+
+
2 arguments:
+
+
+
+
3 arguments:
+
+
+
+
open:
+
+
+
+
close:
+
+
+
+
separators:
+
+
+
+
open, close, one separator:
+
+
+
+
several separators:
+
+
+
+
multiple characters in open/close attributes:
+
+
+
+
too few separators (last one is repeated):
+
+
+
+
too many separators (excess is ignored):
+
+
+
+
no separators (no <mo> separators should be output):
+
+
+
+
whitespace (they should be ignored):
+
+
+
+
surrogate pairs (each pair should be treated as one character):
+
+
+
+
Other attributes (they should be attached to the expanded mrow):
+
+
+
+
+
Invalid rtl attribute (it should not be attached to the expanded mrow):
+
+
+
+
+
+
diff --git a/mfenced.js b/mfenced.js
new file mode 100644
index 0000000..e657019
--- /dev/null
+++ b/mfenced.js
@@ -0,0 +1,118 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode:nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 tw=80: */
+/*
+ Copyright (c) 2016 Igalia S.L.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+(function () {
+ "use strict";
+
+ const namespaceURI = "http://www.w3.org/1998/Math/MathML";
+
+ function collapseWhiteSpace(text) {
+ // Collapse the whitespace as specified by the MathML specification.
+ // See https://www.w3.org/TR/MathML/chapter2.html#fund.collapse
+ return text.replace(/^[\s]+|[\s]+$/g, '').replace(/[\s]+/g, ' ');
+ }
+
+ function newOperator(text, separator) {
+ // Create text or text.
+ let operator = document.createElementNS(namespaceURI, "mo");
+ operator.appendChild(document.createTextNode(text));
+ operator.setAttribute(separator ? "separator" : "fence", "true");
+ return operator;
+ }
+
+ function newMrow() {
+ // Create an empty .
+ return document.createElementNS(namespaceURI, "mrow");
+ }
+
+ function getSeparatorList(text) {
+ // Split the separators attribute into a list of characters.
+ // We ignore whitespace and handle surrogate pairs.
+ if (text === null) {
+ return [","];
+ }
+ let separatorList = [];
+ for (let i = 0; i < text.length; i++) {
+ if (!/\s/g.test(text.charAt(i))) {
+ let c = text.charCodeAt(i);
+ if (c >= 0xD800 && c < 0xDC00 && i + 1 < text.length) {
+ separatorList.push(text.substr(i, 2));
+ i++;
+ } else {
+ separatorList.push(text.charAt(i));
+ }
+ }
+ }
+ return separatorList;
+ }
+
+ function shouldCopyAttribute(attribute) {
+ // The and elements have the same attributes except
+ // that dir is only accepted on and open/close/separators are
+ // only accepted on . See https://www.w3.org/Math/RelaxNG/
+ const excludedAttributes = ["dir", "open", "close", "separators"];
+ return attribute.namespaceURI || !excludedAttributes.includes(attribute.localName);
+ }
+
+ function expandFencedElement(mfenced) {
+ // Return an element representing the expanded .
+ // See https://www.w3.org/TR/MathML/chapter3.html#presm.fenced
+ let outerMrow = newMrow();
+ outerMrow.appendChild(newOperator(collapseWhiteSpace(mfenced.getAttribute("open") || "(")));
+ if (mfenced.childElementCount === 1) {
+ outerMrow.appendChild(mfenced.firstElementChild.cloneNode(true));
+ } else if (mfenced.childElementCount > 1) {
+ let separatorList = getSeparatorList(mfenced.getAttribute("separators")),
+ innerMrow = newMrow(),
+ child = mfenced.firstElementChild;
+ while (child) {
+ innerMrow.appendChild(child.cloneNode(true));
+ child = child.nextElementSibling;
+ if (child && separatorList.length) {
+ innerMrow.appendChild(newOperator(separatorList.length > 1 ? separatorList.shift() : separatorList[0]));
+ }
+ }
+ outerMrow.appendChild(innerMrow);
+ }
+ outerMrow.appendChild(newOperator(collapseWhiteSpace(mfenced.getAttribute("close") || ")")));
+ for (let i = 0; i < mfenced.attributes.length; i++) {
+ let attribute = mfenced.attributes[i];
+ if (shouldCopyAttribute(attribute)) {
+ outerMrow.setAttributeNS(attribute.namespaceURI, attribute.localName, attribute.value);
+ }
+ }
+ return outerMrow;
+ }
+
+ window["expandMathMLFencedElements"] = function () {
+ // Replace all the elements with their expanded equivalent.
+ let mfencedElements = document.body.getElementsByTagNameNS(namespaceURI, "mfenced");
+ while (mfencedElements.length) {
+ let fenced = mfencedElements[0];
+ fenced.parentNode.replaceChild(expandFencedElement(fenced), fenced);
+ }
+ };
+
+ window.addEventListener("load", window["expandMathMLFencedElements"]);
+}());