diff --git a/app/assets/javascripts/pretty-text/engines/markdown-it/bbcode-block.js.es6 b/app/assets/javascripts/pretty-text/engines/markdown-it/bbcode-block.js.es6
index 51050f9206d..db00edbea7e 100644
--- a/app/assets/javascripts/pretty-text/engines/markdown-it/bbcode-block.js.es6
+++ b/app/assets/javascripts/pretty-text/engines/markdown-it/bbcode-block.js.es6
@@ -11,6 +11,8 @@ function trailingSpaceOnly(src, start, max) {
   return true;
 }
 
+const ATTR_REGEX = /(([a-z0-9]*)\s*=)/ig;
+
 // parse a tag [test a=1 b=2] to a data structure
 // {tag: "test", attrs={a: "1", b: "2"}
 export function parseBBCodeTag(src, start, max, multiline) {
@@ -71,31 +73,23 @@ export function parseBBCodeTag(src, start, max, multiline) {
     // trivial parser that is going to have to be rewritten at some point
     if (raw) {
 
-      // reading a key 0, reading a val = 1
-      let readingKey = true;
-      let startSplit = 0;
-      let key;
+      let match, key, val;
 
-      for(i=0; i<raw.length; i++) {
-        if (raw[i] === '=' || i === (raw.length-1)) {
-          // one more offset to allow room to capture last
-          if (raw[i] !== '=' || i === (raw.length-1)) {
-            i+=1;
-          }
-
-          let cur = raw.slice(startSplit, i).trim();
-          if (readingKey) {
-            key =  cur || '_default';
-          } else {
-            let val = raw.slice(startSplit, i).trim();
-            if (val && val.length > 0) {
-              val = val.replace(/^["'](.*)["']$/, '$1');
-              attrs[key] = val;
-            }
-          }
-          readingKey = !readingKey;
-          startSplit = i+1;
+      while(match = ATTR_REGEX.exec(raw)) {
+        if (key) {
+          val = raw.slice(attrs[key],match.index) || '';
+          val = val.trim();
+          val = val.replace(/^["'](.*)["']$/, '$1');
+          attrs[key] = val;
         }
+        key = match[2] || '_default';
+        attrs[key] = match.index + match[0].length;
+      }
+
+      if (key) {
+        val = raw.slice(attrs[key]);
+        val = val.replace(/^["'](.*)["']$/, '$1');
+        attrs[key] = val;
       }
     }
 
diff --git a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6 b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
index b7b41648e1e..65de3ce659b 100644
--- a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
+++ b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
@@ -127,7 +127,7 @@ const rule = {
 
     WHITELISTED_ATTRIBUTES.forEach(name => {
       if (attrs[name]) {
-        attributes[DATA_PREFIX + name] = attrs[name];
+        attributes.push([DATA_PREFIX + name, attrs[name]]);
       }
     });
 
@@ -136,9 +136,9 @@ const rule = {
     }
 
     // we might need these values later...
-    let min = parseInt(attributes[DATA_PREFIX + "min"], 10);
-    let max = parseInt(attributes[DATA_PREFIX + "max"], 10);
-    let step = parseInt(attributes[DATA_PREFIX + "step"], 10);
+    let min = parseInt(attrs["min"], 10);
+    let max = parseInt(attrs["max"], 10);
+    let step = parseInt(attrs["step"], 10);
 
     let header = [];
 
@@ -157,7 +157,7 @@ const rule = {
     header.push(token);
 
     // generate the options when the type is "number"
-    if (attributes[DATA_PREFIX + "type"] === "number") {
+    if (attrs["type"] === "number") {
       // default values
       if (isNaN(min)) { min = 1; }
       if (isNaN(max)) { max = md.options.discourse.pollMaximumOptions; }
@@ -172,7 +172,7 @@ const rule = {
       header.push(token);
 
       for (let o = min; o <= max; o += step) {
-        token = new state.Token('list_item_open', '', 1);
+        token = new state.Token('list_item_open', 'li', 1);
         items.push([token,  String(o)]);
         header.push(token);
 
@@ -180,7 +180,7 @@ const rule = {
         token.content = String(o);
         header.push(token);
 
-        token = new state.Token('list_item_close', '', -1);
+        token = new state.Token('list_item_close', 'li', -1);
         header.push(token);
       }
       token = new state.Token('bullet_item_close', '', -1);
diff --git a/plugins/poll/spec/lib/pretty_text_spec.rb b/plugins/poll/spec/lib/pretty_text_spec.rb
index 417a2989f4f..083a9b2d9f4 100644
--- a/plugins/poll/spec/lib/pretty_text_spec.rb
+++ b/plugins/poll/spec/lib/pretty_text_spec.rb
@@ -12,6 +12,34 @@ describe PrettyText do
       SiteSetting.enable_experimental_markdown_it = true
     end
 
+    it 'supports multi choice polls' do
+      cooked = PrettyText.cook <<~MD
+        [poll type=multiple min=1 max=3 public=true]
+        * option 1
+        * option 2
+        * option 3
+        [/poll]
+      MD
+
+      expect(cooked).to include('class="poll"')
+      expect(cooked).to include('data-poll-status="open"')
+      expect(cooked).to include('data-poll-name="poll"')
+      expect(cooked).to include('data-poll-type="multiple"')
+      expect(cooked).to include('data-poll-min="1"')
+      expect(cooked).to include('data-poll-max="3"')
+      expect(cooked).to include('data-poll-public="true"')
+    end
+
+    it 'can dynamically generate a poll' do
+
+      cooked = PrettyText.cook <<~MD
+        [poll type=number min=1 max=20 step=1]
+        [/poll]
+      MD
+
+      expect(cooked.scan('<li').length).to eq(20)
+    end
+
     it 'can properly bake 2 polls' do
       md = <<~MD
         this is a test
@@ -88,7 +116,7 @@ describe PrettyText do
       cooked = PrettyText.cook md
 
       expected = <<~MD
-        <div class="poll" data-poll-status="open" data-poll-name="poll">
+        <div class="poll" data-poll-status="open" data-poll-name="poll" data-poll-type="multiple">
         <div>
         <div class="poll-container">
         <ol>
diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb
index 3c4089d058f..ea6c749d5d6 100644
--- a/spec/components/pretty_text_spec.rb
+++ b/spec/components/pretty_text_spec.rb
@@ -908,7 +908,6 @@ HTML
         <img src="http://png.com/my.png" alt="stuff"><br>
         <img src="http://png.com/my.png" alt width="110" height="50"></p>
       HTML
-      puts cooked
 
       expect(cooked).to eq(html.strip)
     end