protected function extractChildElements(\DOMNode $elem, $skipWhitespace = false, $parentIsBlock = false) { /** @var \DOMNode $child */ $first = true; foreach ($elem->childNodes as $child) { if ($skipWhitespace && $child->nodeName === '#text' && ctype_space($child->nodeValue)) { // do nothing } elseif ($child->nodeName === '#text') { $this->elements[] = new ElementEvent(ElementEvents::TEXT, $this->normalizeSpace($child->nodeValue, $parentIsBlock && $first), $this->attributes->values()); } else { switch ($child->nodeName) { case 'p': $this->elements[] = new ElementEvent(ElementEvents::PARAGRAPH_START); $this->extractChildElements($child, false, true); $this->elements[] = new ElementEvent(ElementEvents::PARAGRAPH_END); break; case 'ul': $this->attributes->setMany(['list_depth' => $this->attributes->get('list_depth', 0) + 1, 'list_numbered' => false]); $this->elements[] = new ElementEvent(ElementEvents::LIST_START, '', [ElementAttributes::NUMBERED => false]); $this->extractChildElements($child, true); $this->elements[] = new ElementEvent(ElementEvents::LIST_END, '', [ElementAttributes::NUMBERED => false]); $this->attributes->undo(); break; case 'ol': $this->attributes->setMany(['list_depth' => $this->attributes->get('list_depth', 0) + 1, 'list_numbered' => true]); $this->elements[] = new ElementEvent(ElementEvents::LIST_START, '', [ElementAttributes::NUMBERED => true]); $this->extractChildElements($child, true); $this->elements[] = new ElementEvent(ElementEvents::LIST_END, '', [ElementAttributes::NUMBERED => true]); $this->attributes->undo(); break; case 'li': $this->elements[] = new ElementEvent(ElementEvents::LIST_ITEM_START, '', [ElementAttributes::DEPTH => $this->attributes['list_depth'] - 1, ElementAttributes::NUMBERED => $this->attributes['list_numbered']]); $this->extractChildElements($child, false, true); $this->elements[] = new ElementEvent(ElementEvents::LIST_ITEM_END, '', [ElementAttributes::DEPTH => $this->attributes['list_depth'] - 1, ElementAttributes::NUMBERED => $this->attributes['list_numbered']]); break; case 'table': $this->elements[] = new ElementEvent(ElementEvents::TABLE_START); $this->extractChildElements($child, true); $this->elements[] = new ElementEvent(ElementEvents::TABLE_END); break; case 'tr': $this->elements[] = new ElementEvent(ElementEvents::TABLE_ROW_START); $this->extractChildElements($child, true); $this->elements[] = new ElementEvent(ElementEvents::TABLE_ROW_END); break; case 'th': case 'td': $this->elements[] = new ElementEvent(ElementEvents::TABLE_CELL_START, '', [ElementAttributes::HEADER => $child->nodeName === 'th']); $this->extractChildElements($child); $this->elements[] = new ElementEvent(ElementEvents::TABLE_CELL_END, '', [ElementAttributes::HEADER => $child->nodeName === 'th']); break; case 'tbody': case 'thead': $this->extractChildElements($child, true); break; case 'h1': $this->elements[] = new ElementEvent(ElementEvents::TITLE, $this->normalizeSpace($child->nodeValue), [ElementAttributes::DEPTH => 0]); break; case 'h2': $this->elements[] = new ElementEvent(ElementEvents::TITLE, $this->normalizeSpace($child->nodeValue), [ElementAttributes::DEPTH => 1]); break; case 'h3': $this->elements[] = new ElementEvent(ElementEvents::TITLE, $this->normalizeSpace($child->nodeValue), [ElementAttributes::DEPTH => 2]); break; case 'h4': $this->elements[] = new ElementEvent(ElementEvents::TITLE, $this->normalizeSpace($child->nodeValue), [ElementAttributes::DEPTH => 3]); break; case 'h5': $this->elements[] = new ElementEvent(ElementEvents::TITLE, $this->normalizeSpace($child->nodeValue), [ElementAttributes::DEPTH => 4]); break; case 'h6': $this->elements[] = new ElementEvent(ElementEvents::TITLE, $this->normalizeSpace($child->nodeValue), [ElementAttributes::DEPTH => 5]); break; case 'div': $this->extractChildElements($child, true); break; case 'span': $this->extractChildElements($child); break; case 'strong': case 'b': $this->attributes[ElementAttributes::BOLD] = true; $this->extractChildElements($child); $this->attributes->undo(); break; case 'em': case 'i': $this->attributes[ElementAttributes::ITALIC] = true; $this->extractChildElements($child); $this->attributes->undo(); break; case 'u': $this->attributes[ElementAttributes::UNDERLINE] = 'single'; $this->extractChildElements($child); $this->attributes->undo(); break; case 'del': $this->attributes[ElementAttributes::STRIKETHROUGH] = true; $this->extractChildElements($child); $this->attributes->undo(); break; case 'sup': $this->attributes[ElementAttributes::SUPERSCRIPT] = true; $this->extractChildElements($child); $this->attributes->undo(); break; case 'sub': $this->attributes[ElementAttributes::SUBSCRIPT] = true; $this->extractChildElements($child); $this->attributes->undo(); break; case 'a': $href = $child->attributes->getNamedItem('href')->nodeValue; $this->attributes[ElementAttributes::LINK] = $href; $this->elements[] = new ElementEvent(ElementEvents::LINK, $this->normalizeSpace($child->nodeValue), $this->attributes->values()); $this->attributes->undo(); break; case 'img': $this->elements[] = new ElementEvent(ElementEvents::IMAGE, $child->attributes->getNamedItem('src')->nodeValue); break; default: throw new \RuntimeException("Element of type {$child->nodeName} not supported"); break; } } $first = false; } }
public function onElement(ElementEvent $event) { if (null === $this->section) { throw new \RuntimeException("No document section provided"); } /** @var AbstractContainer $current */ $current = $this->context->get('current', $this->section); if ($current === $this->section) { $this->dispatcher->dispatch(DocumentEvents::BEFORE_ROOT_ELEMENT, new DocumentConstructionEvent($this->section)); } switch ($event->getElement()) { case ElementEvents::TITLE: /** @var Section $current */ $title = $current->addTitle($event->getContent()); $this->dispatcher->dispatch(DocumentEvents::TITLE_CREATED, new DocumentEvent($this->section, $title, $event)); break; case ElementEvents::PARAGRAPH_START: $this->context->setMany(['current' => $current->addTextRun(), 'event' => $event]); break; case ElementEvents::TABLE_START: $this->context->setMany(['current' => $current->addTable(['width' => 5000, 'unit' => 'pct']), 'event' => $event]); break; case ElementEvents::TABLE_ROW_START: /** @var Table $current */ $this->context->setMany(['current' => $current->addRow(), 'event' => $event]); break; case ElementEvents::TABLE_CELL_START: /** @var Row $current */ $this->context->setMany(['current' => $current->addCell(5000), 'event' => $event]); break; case ElementEvents::LIST_END: // close the last item in the list if ($current instanceof ListItemRun) { $this->dispatcher->dispatch(DocumentEvents::LIST_ITEM_CREATED, new DocumentEvent($this->section, $current, $this->context['event'])); $this->context->undo(); } break; case ElementEvents::LIST_ITEM_END: case ElementEvents::LIST_START: break; case ElementEvents::LIST_ITEM_START: // close the previous list item if ($current instanceof ListItemRun) { $this->dispatcher->dispatch(DocumentEvents::LIST_ITEM_CREATED, new DocumentEvent($this->section, $current, $this->context['event'])); $this->context->undo(); $current = $this->context->get('current', $this->section); } $is_numbered = $event->getAttribute(ElementAttributes::NUMBERED, false); $this->context->setMany(['current' => $current->addListItemRun($event->getAttribute(ElementAttributes::DEPTH), ['listType' => $is_numbered ? ListItem::TYPE_NUMBER : ListItem::TYPE_BULLET_FILLED]), 'event' => $event]); break; case ElementEvents::TEXT: $current->addText($event->getContent(), $event->getAttributes()); break; case ElementEvents::IMAGE: $image = $current->addImage($event->getContent()); $this->dispatcher->dispatch(DocumentEvents::IMAGE_CREATED, new DocumentEvent($this->section, $image, $event)); break; case ElementEvents::LINK: $link = $current->addLink($event->getAttribute(ElementAttributes::LINK), $event->getContent()); $this->dispatcher->dispatch(DocumentEvents::LINK_CREATED, new DocumentEvent($this->section, $link, $event)); break; case ElementEvents::TABLE_END: $this->dispatcher->dispatch(DocumentEvents::TABLE_CREATED, new DocumentEvent($this->section, $current, $this->context['event'])); $this->context->undo(); break; case ElementEvents::PARAGRAPH_END: $this->dispatcher->dispatch(DocumentEvents::PARAGRAPH_CREATED, new DocumentEvent($this->section, $current, $this->context['event'])); $this->context->undo(); break; case ElementEvents::TABLE_ROW_END: $this->dispatcher->dispatch(DocumentEvents::TABLE_ROW_CREATED, new DocumentEvent($this->section, $current, $this->context['event'])); $this->context->undo(); break; case ElementEvents::TABLE_CELL_END: $this->dispatcher->dispatch(DocumentEvents::TABLE_CELL_CREATED, new DocumentEvent($this->section, $current, $this->context['event'])); $this->context->undo(); break; default: throw new \RuntimeException("Cannot handle element of type '{$event->getElement()}'"); } $current = $this->context->get('current', $this->section); if ($current === $this->section) { $this->dispatcher->dispatch(DocumentEvents::AFTER_ROOT_ELEMENT, new DocumentConstructionEvent($this->section)); } }