PHPackages                             simbiat/nl2tag - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. simbiat/nl2tag

ActiveLibrary

simbiat/nl2tag
==============

Class to convert new lines to different HTML tags

2.0.0+20250413(1y ago)11.5k↓42.1%[1 issues](https://github.com/Simbiat/nl2tag/issues)AGPL-3.0-or-laterPHPPHP ^8.3

Since Nov 19Pushed 9mo ago1 watchersCompare

[ Source](https://github.com/Simbiat/nl2tag)[ Packagist](https://packagist.org/packages/simbiat/nl2tag)[ Docs](https://github.com/Simbiat/nl2tag)[ RSS](/packages/simbiat-nl2tag/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)DependenciesVersions (14)Used By (0)

nl2tag
======

[](#nl2tag)

Class to convert new lines to various HTML tags: `br`, `p` and `li`. It can also help with creating nice-looking changelogs (based on `li` wrapping).

Why?
----

[](#why)

Initially the idea was for a version of `nl2br`, but for paragraphs. If you google (or [Stackoverflow](https://stackoverflow.com/questions/3738124/nl2br-for-paragraphs)) for it, you can find several easy approaches for this, but all of those that I found have at least some flaws:

1. They do not check whether there is already a `p` tag around respective paragraph. This can result in extra paragraphs. At least some browsers will then show this as extra paragraphs, meaning `line1line2line3` will result in 3 paragraphs, which may not be the intention.
2. In fact, there is a bunch of tags, that are not expected inside of `p`, as per the [spec](https://html.spec.whatwg.org/#phrasing-content) of `phrasing content`. Or rather there is a limited set of tags, tha can be.
3. They will change new lines inside tags, where you want to preserve new lines as is. `pre` and `textarea` are the ones, where you could generally want that. `code`, `samp`, `kbd` and `var` are an example of other common values, but technically it can be any tag with `white-space` CSS property set to either `pre`, `pre-wrap`, `pre-line` or `break-spaces`.
4. They usually only check for `\r\n` or just `\r` or `\n`, while there are actually more symbols, that would mean new line, and they also have respective HTML entities, which can easily occur in HTML string.

When you are working with HTML, these flaws can become an issue, thus I wanted to find a way to circumvent them, hence this library. But at early stage of conceptualization, I realized, that it would make sense to also do something similar for `li` items and even `br`.
I also wanted to be able to create lists for changelogs, that could be styled nicely with CSS, and since that would imply the same logic as we have for `nl2li` it's done in the same class.

Limitations
-----------

[](#limitations)

1. Performance, obviously, since the processing is trying to be "smart", has various checks here and there, they do take time. It will be slower than other solutions, although most likely you will not notice it, still.
2. No recursion. Meaning, that, if you have something line

```
text
text2
text3
text4
```

You will get

```
texttext2text3text4
```

And not

```
texttext2text3text4
```

This is because recursion would require traversal of the DOM tree, which implies conversion into `DOMDocument`, but converting a string can add extra tags in quite unpredictable places, as it tries to "fix" the potentially broken document. Such modification can result in vastly different results from what would expect, and can't be predicted. Considering expected use of the library, using only 1 level should be enough.

3. Tags like `area`, `link`, `main` and `meta` will not be wrapped, even though they are valid phrasing (for `p`) or flow (for `li`) content, because they are **conditionally** valid, and I do not believe it's worth to overcomplicate the logic for them, considering the expected use of the library. If required, they can be included through respective settings, though.
4. I am sure there is some combination of tags and/or new lines that will result in "corrupt" output, if processed by this class, since it relies on regex. Alternative would be to rely on PHP's `DOMDocument` and pals.

Constants
---------

[](#constants)

Class has some public constants, in case you would require something similar in other classes in your code:

1. `NEW_LINES_REGEX` - list of entities that are considered "new lines". It's a string with `|` as separator to be used directly in regex.
2. `VOID_ELEMENTS` - array of standard HTML5 tags that are considered "self-closing", that is do not require a closing tag.
3. `PRESERVE_SPACE_IN` - array of standard HTML5 tags that usually imply preservation of whitespace in them.
4. `PHRASING_CONTENT` - array of standard HTML5 tags that are considered as "phrasing content" and do not have any additional conditions for that.
5. `FLOW_CONTENT` - similar tp `PHRASING_CONTENT`, but for "flow content" as per [spec](https://html.spec.whatwg.org/#flow-content).

Options
-------

[](#options)

The class has some options, that can be changed (public variables):

1. `$preserve_spaces_in` - use to adjust list of tags inside which you want to preserve spaces. Can be an empty array, which will mean, that new lines inside all tags may be replaced accordingly.
2. `$phrasing_content` - use to adjust the list of tags, that are considered as phrasing content. Tags from respective constant cannot be removed. The most likely scenario for updating this list is when you have custom HTML elements.
3. `$flow_content` - use to adjust the list of tags, that are considered as flow content. Tags from respective constant cannot be removed. The most likely scenario for updating this list is when you have custom HTML elements.
4. `$wrapper_only` - list of tags, that are treated as wrappers only, like `table` and the like. They can have new lines between tags, and unless excluded, you can get excessive `` tags, if `$situational_br` is turned on.
5. `$inside_wrappers_only` - list of tags, that only happen within respective wrappers and can have meaningful new lines in them, like `td`, `th`. Used specifically in conjunction with `$wrapper_only` setting.
6. `$situational_br` - boolean flag (default is `true`) indicating, whether class can replace newlines with `` when a new line is found inside the content of the tag, and it's not the one where we preserve whitespace. Sorry if the name is not clear enough, but was not able to come up with something else more proper.
7. `$collapse_new_lines` - boolean flag (default is `true`) indicating, whether class will try to collapse new lines. This means that `` will be replaced with ``, `` tags between `` tags will be removed (that is **only** `12` will change into `12`) and paragraphs consisting only of whitespace will be removed, too. I believe, that in most cases, you would want this enabled, but there may be situations, when you would like to avoid it, for example, when you can have respective combinations inside ``.
8. `$preserve_non_breaking_space` - boolean flag, indicating, whether class will preserve empty paragraphs with non-breaking space (`&nbsp`, which gets converted to proper character during processing). If set to `true` (default is `false`), lines like `&nbsp` will not be removed, even if `$collapse_new_lines` is also `true`.

Usage
-----

[](#usage)

Create the object:

```
$object = (new \Simbiat\HTML\NL2Tag);
```

Adjust settings, if required. Setters can be chained. Below example will add `custom-element` as phrasing content and set that only `textarea` needs to preserve whitespace.

```
$object->setPhrasingContent(['custom-element'])->setPreserveSpacesIn(['textarea']);
```

Call appropriate function, while sending a string to it.

```
$result = $object->nl2p('string');
```

```
asdasda
asdsada
test
test2

test3afdas
tgitjsglsjdgdsjglsd
span after_span
test4
test5
test6
test7
```

passed through `nl2p()` will provide this result:

```
asdasdaasdsadatesttest2
test3afdastgitjsglsjdgdsjglsdspan after_spantest4test5test6test7
```

If passed through `nl2br()`, it will provide this result:

```
asdasdaasdsadatesttest2
test3afdastgitjsglsjdgdsjglsdspan after_spantest4test5test6test7
```

If passed through `nl2li()`, it will provide this result:

```
asdasdaasdsadatesttest2
test3afdastgitjsglsjdgdsjglsdspan after_spantest4test5test6test7
```

`nl2li()` wraps string in `ul` by default, but you can pass a second optional string (`menu`, `ol`, `ul`), which will allow to change the type of list you get.
There is also a variation of `nl2li()`, called `changelog()`. Logic is very similar, but it also checks for the 1st character in text value of a line and then adds appropriate class to ``. If character is not one of `*`, `+` or `-` a sublist (that is new ``) will be started until another line like that or the end of string will be encountered.
A string like this:

```
* Some general change in logic
List of changes in a specific module:
+ Feature added
- Feature removed
List of changes in another module:
* Some general change
```

Will result in HTML like this (whitespace added between tags for readability, real string will not have them):

```

    Some general change in logic
    List of changes in a specific module:

        Feature added
        Feature removed

    List of changes in another module:

        Some general change

```

If styled, you can get a nice-looking changelog, for example, like what you see [here](https://www.simbiat.eu/talks/posts/123).

###  Health Score

36

—

LowBetter than 82% of packages

Maintenance32

Infrequent updates — may be unmaintained

Popularity22

Limited adoption so far

Community4

Small or concentrated contributor base

Maturity68

Established project with proven stability

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~73 days

Recently: every ~92 days

Total

13

Last Release

400d ago

Major Versions

1.3.7+20250209 → 2.0.0+202504132025-04-13

PHP version history (2 changes)1.0.0+20221119PHP ^8

1.3.4+20240407PHP ^8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/6022665?v=4)[Dmitrii Kustov](/maintainers/Simbiat)[@Simbiat](https://github.com/Simbiat)

---

Tags

phphtmlnl2brnl2p

### Embed Badge

![Health badge](/badges/simbiat-nl2tag/health.svg)

```
[![Health](https://phpackages.com/badges/simbiat-nl2tag/health.svg)](https://phpackages.com/packages/simbiat-nl2tag)
```

###  Alternatives

[daandesmedt/phpheadlesschrome

A PHP wrapper for using Google Chrome Headless mode. Convert URL or HTML to a PDF / screenshot. Easy to use and OOP interfaced.

92233.1k](/packages/daandesmedt-phpheadlesschrome)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
