Skip to content

Instantly share code, notes, and snippets.

@wakaba
Last active December 30, 2015 21:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wakaba/7887273 to your computer and use it in GitHub Desktop.
Save wakaba/7887273 to your computer and use it in GitHub Desktop.
javascript:
/* You are granted a license to use, reproduce and create derivative works of this document. */
function walk (root, enter, exit) {
var node = root;
start: while (node) {
enter(node);
if (node.firstChild) {
node = node.firstChild;
continue start;
}
while (node) {
exit(node);
if (node == root) {
node = null;
} else if (node.nextSibling) {
node = node.nextSibling;
continue start;
} else {
node = node.parentNode;
}
}
}
}
var HTML_NS = 'http://www.w3.org/1999/xhtml';
function Outline (el) {
this.element = el;
this.sections = [];
}
Outline.prototype.getLastSection = function () {
return this.lastSection;
};
Outline.prototype.appendSection = function (sect) {
this.sections.push (sect);
this.lastSection = sect;
};
function Section (el) {
this.element = el; // or null
this.heading = null;
this.sections = [];
}
Section.prototype.appendSection = function (sect) {
this.sections.push (sect);
sect.parentSection = this;
this.lastSection = sect;
};
Section.prototype.getLastSection = function () {
return this.lastSection;
};
Section.prototype.appendOutline = function (o) {
this.sections.push (o);
/*
for (var i = 0; i < o.length; i++) {
o[i].parentSection = this;
}
*/
};
function getRank (el) {
if (!el || !el.localName) return 0;
if (el.namespaceURI === HTML_NS && /^h[1-6]$/.test (el.localName)) {
return 7 - parseInt (el.localName.substring (1, 2));
} else {
if (el.getElementsByTagName ('h1')[0]) return 6;
if (el.getElementsByTagName ('h2')[0]) return 5;
if (el.getElementsByTagName ('h3')[0]) return 4;
if (el.getElementsByTagName ('h4')[0]) return 3;
if (el.getElementsByTagName ('h5')[0]) return 2;
if (el.getElementsByTagName ('h6')[0]) return 1;
return 6;
}
} // getRank
function isSectioningContent (el) {
return (el.namespaceURI === HTML_NS && /^(?:article|aside|nav|section)$/.test(el.localName));
}
function isHeadingContent (el) {
return (el.namespaceURI === HTML_NS && /^(?:h[1-6]|hgroup)$/.test(el.localName));
}
function isSectioningRoot (el) {
return (el.namespaceURI === HTML_NS && /^(?:blockquote|body|details|dialog|fieldset|figure|td)$/.test(el.localName));
}
function createOutline (root) {
var currentOutlineTarget = null;
var currentSection = null;
var stack = [];
var outlines = [];
var getOutlineByElement = function (el) {
for (var i = 0; i < outlines.length; i++) {
if (outlines[i].element === el) return outlines[i];
}
return null;
};
walk (root, function (node) {
var stackTop = stack[stack.length - 1];
if (stackTop && (isHeadingContent(stackTop) || (stackTop.namespaceURI === HTML_NS && stackTop.hasAttribute('hidden')))) {
//
} else if (node.namespaceURI === HTML_NS && node.hasAttribute ('hidden')) {
stack.push (node);
} else if (isSectioningContent (node) || isSectioningRoot (node)) {
if (currentOutlineTarget && !currentSection.heading) {
currentSection.heading = {};
}
if (currentOutlineTarget) {
stack.push (currentOutlineTarget);
}
currentOutlineTarget = node;
currentSection = new Section (currentOutlineTarget);
var outline = new Outline (currentOutlineTarget);
outline.appendSection (currentSection);
outlines.push (outline);
} else if (isHeadingContent (node)) {
if (!currentSection.heading) {
currentSection.heading = node;
} else if (getRank (node) >= getRank (getOutlineByElement (currentOutlineTarget).getLastSection ().heading)) {
var newSection = new Section;
getOutlineByElement (currentOutlineTarget).appendSection (newSection);
currentSection = newSection;
currentSection.heading = node;
} else {
var candidateSection = currentSection;
HEADING_LOOP: while (true) {
if (getRank (node) < getRank (candidateSection.heading)) {
var newSection = new Section;
candidateSection.appendSection (newSection);
currentSection = newSection;
newSection.heading = node;
break HEADING_LOOP;
}
candidateSection = candidateSection.parentSection;
} // HEADING_LOOP
}
stack.push (node);
} else {
//
}
}, function (node) {
var stackTop = stack[stack.length - 1];
if (stackTop === node) {
stack.pop ();
} else if (stackTop && (isHeadingContent (stackTop) || (stackTop.namespaceURI === HTML_NS && stackTop.hasAttribute('hidden')))) {
//
} else if (isSectioningContent (node) && stack.length) {
if (!currentSection.heading) {
currentSection.heading = {};
}
currentOutlineTarget = stack.pop ();
currentSection = getOutlineByElement (currentOutlineTarget).getLastSection ();
currentSection.appendOutline (getOutlineByElement (node));
} else if (isSectioningRoot (node) && stack.length) {
if (!currentSection.heading) {
currentSection.heading = {};
}
currentOutlineTarget = stack.pop ();
currentSection = getOutlineByElement (currentOutlineTarget).getLastSection ();
FINDING_DEEPEST_CHILD: while (true) {
var nextLastSection = currentSection.getLastSection ();
if (!nextLastSection) break FINDING_DEEPEST_CHILD;
currentSection = nextLastSection;
}
} else if (isSectioningContent (node) || isSectioningRoot (node)) {
if (!currentSection.heading) {
currentSection.heading = {};
}
} else {
//
}
});
return outlines.filter (function (o) { return isSectioningRoot (o.element) });
} // createOutline
var outlines = createOutline (document.body);
console.log (outlines);
showOutline (outlines[0]);
function showOutline (outline) {
var container = document.createElement ('aside');
container.innerHTML = '<style scoped>.outline-list { background: white; color: black; display: block; position: relative; float: none; border: 2px blue solid; padding: 0.5em; font-size: 1rem; font-style: normal; font-weight: normal; text-align: left; z-index: 21000000; } .outline-list ul { margin-left: 2em }</style><ul class=outline-list></ul>';
dumpSections (outline.sections, container.lastChild);
document.body.appendChild (container);
container.scrollIntoView ();
} // showOutline
function dumpSections (sections, parent) {
for (var i = 0; i < sections.length; i++) {
var section = sections[i];
if (section instanceof Outline) {
dumpSections (section.sections, parent);
continue;
}
var sectionEl = document.createElement ('li');
sectionEl.innerHTML = '<span></span>';
sectionEl.firstChild.textContent = section.heading ? section.heading.textContent || '(Section)' : '(Section)';
sectionEl.firstChild.sectionTargetElement = section.element || section.heading;
sectionEl.firstChild.title = 'Section: ' + (section.element ? section.element.nodeName : 'Implied') + "\n"
+ 'Heading: ' + (section.heading.nodeName || 'Implied');
sectionEl.firstChild.onclick = function (ev) { ev.target.sectionTargetElement.scrollIntoView () };
if (section.sections.length) {
var ul = document.createElement ('ul');
dumpSections (section.sections, ul);
sectionEl.appendChild (ul);
}
parent.appendChild (sectionEl);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment