Serializes paragraph element.

    This is the most important serializer of them all.

def serialize_paragraph(ctx, document, par, root, embed=True):
    """Serializes paragraph element.

    This is the most important serializer of them all.    
    """

    style = get_style(document, par)

    elem = etree.Element('p')

    if ctx.options['embed_styles']:
        _style = get_style_css(ctx, par)

        if _style != '':
            elem.set('style', _style)

    else:
        _style = ''

    if style:
        elem.set('class', get_css_classes(document, style))

    max_font_size = get_style_fontsize(par)

    if style:
        max_font_size = _get_font_size(document, style)

    for el in par.elements:
        _serializer =  ctx.get_serializer(el)

        if _serializer:
            _serializer(ctx, document, el, elem)

        if isinstance(el, doc.Text):
            children = list(elem)
            _text_style = get_style_css(ctx, el)
            _text_class = el.rpr.get('style', '').lower()

            if _text_class == '':
                __s = get_style(document, par)
                if __s is not None:
                    _text_class = get_style_name(__s).lower()

            if get_style_fontsize(el) > max_font_size:
                max_font_size = get_style_fontsize(el)

            if 'superscript' in el.rpr:
                new_element = etree.Element('sup')
                new_element.text = el.value()
            elif 'subscript' in el.rpr:
                new_element = etree.Element('sub')
                new_element.text = el.value()               
            elif 'b' in el.rpr or 'i' in el.rpr or 'u' in el.rpr:                
                new_element = None
                _element = None

                def _add_formatting(f, new_element, _element):
                    if f in el.rpr:
                        _t = etree.Element(f)

                        if new_element is not None:
                            _element.append(_t)
                            _element = _t
                        else:
                            new_element = _t
                            _element = new_element

                    return new_element, _element

                new_element, _element = _add_formatting('b', new_element, _element)
                new_element, _element = _add_formatting('i', new_element, _element)
                new_element, _element = _add_formatting('u', new_element, _element)
                
                _element.text = el.value()

                for comment_id in ctx.opened_comments:
                    document.comments[comment_id].text += ' ' + el.value()
            else:
                new_element = etree.Element('span')
                new_element.text = el.value()
                if ctx.options['embed_styles']:
                    try:
                        new_element.set('class', _text_class)
                    except:
                        pass

                for comment_id in ctx.opened_comments:
                    if comment_id in document.comments:
                        document.comments[comment_id].text += ' ' + el.value()

            if ctx.options['embed_styles']:
                if _text_style != '' and _style != _text_style:
                    new_element.set('style', _text_style)

            # This is for situations when style has options and
            # text is trying to unset them
            # else:            
            #     new_element.set('class', 'noformat')

            was_inserted = False

            if len(children) > 0:
                _child_style = children[-1].get('style') or ''
                _child_class = children[-1].get('class', '')

                if new_element.tag == children[-1].tag and ((_text_class == _child_class or _child_class == '') and (_text_style == _child_style or _child_style == '')) and children[-1].tail is None:
                    txt = children[-1].text or ''
                    txt2 = new_element.text or ''
                    children[-1].text = u'{}{}'.format(txt, txt2)  
                    was_inserted = True

                if not was_inserted:
                    if _style == _text_style  and new_element.tag == 'span' and (_text_class == _child_class or _child_class == ''):
                        _e = children[-1]

                        txt = _e.tail or ''
                        _e.tail = u'{}{}'.format(txt, new_element.text)
                        was_inserted = True
  
                    if not was_inserted and new_element.tag == 'span' and (_text_class != _child_class):
                        _e = children[-1]
                        txt = _e.tail or ''
                        _e.tail = u'{}{}'.format(txt, new_element.text)
                        was_inserted = True       

            if not was_inserted:
                _child_class = new_element.get('class', '')
                try:
                    _child_class = children[-1].get('class', '')
                except:
                    _child_class = ''

                if _style ==  _text_style  and new_element.tag == 'span' and (_text_class == _child_class):
                    txt = elem.text or ''
                    elem.text = u'{}{}'.format(txt, new_element.text)
                else:
                    if new_element.text != u'':
                        elem.append(new_element)
    
    if not par.is_dropcap() and par.ilvl == None:
        if style:
            if ctx.header.is_header(par, max_font_size, elem, style=style):
                elem.tag = ctx.header.get_header(par, style, elem)
                if par.ilvl == None:        
                    root = close_list(ctx, root)
                    ctx.ilvl, ctx.numid = None, None

                if root is not None:
                    root.append(elem)

                fire_hooks(ctx, document, par, elem, ctx.get_hook('h'))
                return root
        else:
#            Commented part where we only checked for heading if font size
#            was bigger than default font size. In many cases this did not
#            work out well.
#            if max_font_size > ctx.header.default_font_size:
            if True:
                if ctx.header.is_header(par, max_font_size, elem, style=style):
                    if elem.text != '' and len(list(elem)) != 0:
                        elem.tag = ctx.header.get_header(par, max_font_size, elem)

                        if par.ilvl == None:        
                            root = close_list(ctx, root)
                            ctx.ilvl, ctx.numid = None, None


                        if root is not None:
                            root.append(elem)

                        fire_hooks(ctx, document, par, elem, ctx.get_hook('h'))
                        return root

    if len(list(elem)) == 0 and elem.text is None:
        if ctx.options['empty_paragraph_as_nbsp']:
            elem.append(etree.Entity('nbsp'))

    # Indentation is different. We are starting or closing list.
    if par.ilvl != None:        
        root = open_list(ctx, document, par, root, elem)
        return root
    else:
        root = close_list(ctx, root)
        ctx.ilvl, ctx.numid = None, None

    # Add new elements to our root element.
    if root is not None:
        root.append(elem)

    fire_hooks(ctx, document, par, elem, ctx.get_hook('p'))

    return root