[PHP/Regex] match ubb url tag

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Hallo iedereen,

Ik ben momenteel mijn eigen ubb parser aan het schrijven.

Om de ubb tags te vinden gebruik ik de volgede regex:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$regex = 
    '#\[(?:'. // [
        '(/?\w+)'.'|'. // /tag or tag
        '(?:'. // tag=val [p1=val [p2=val ... [pN=val]]]
            '(\w+)'. // tag
            '[ \t]*=[ \t]*'. // =
            '(?:'. // "val", 'val' or val
                '"([^"\n]*)"'.'|'. // "val"
                '\'([^\'\n]*)\''.'|'. // 'val'
                '([^\s\]]+)'. // val
            ')'.
            '(?:'. // <spacer>pN=val
                '[ \t]+'. // <spacer>
                '(\w+)'. // pN
                '[ \t]*=[ \t]*'. // =
                '(?:'. // "val", 'val' or val
                    '"([^"\n]*)"'.'|'. // "val"
                    '\'([^\'\n]*)\''.'|'. // 'val'
                    '([^\s\]]+)'. // val
                ')'.
            ')*'.
        ')'.
    ')\]#'; // ]


Voor simple [ b] [ /b] tags etc. werkt het perfect. Maar voor een tag met params niet.

Hij zou de volgende gewoon moeten kunnen pakken:

code:
1
2
3
[url=<text>]
[url=<text> p1=<text> p2=<text> ... pN=<text>]
[url p1=<text> p2=<text> ... pN=<text>]


met <text> = "text" of 'text' of text

Ziet iemand wat ik fout doe in mijn regex?

edit:
Gewijzid vanaf hier


Hij vind wel dat tags, maar hij matched hem verkeerd. (preg_match)

Met:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[url=www.google.com h='b' g="a" t=d]

krijg ik deze lijst:
    [0] => [url=www.google.com h='b' g="a" t=d]
    [1] => 
    [2] => url
    [3] =>
    [4] => 
    [5] => www.google.com // Tot hier is het goed, maar hierna word het wazig
    [6] => t
    [7] => a
    [8] => b
    [9] => d

terwijl ik dit veracht:
    [0] => [url=www.google.com h='b' g="a" t=d]
    [1] => 
    [2] => url
    [3] =>
    [4] => 
    [5] => www.google.com
    [6] => h
    [7] =>
    [8] => b
    [9] => 
    [10] => g
    [11] => a
    [12] => 
    [13] => 
    [14] => t
    [15] => 
    [16] => 
    [17] => d

[ Voor 35% gewijzigd door vdvleon op 11-06-2009 00:47 ]


Acties:
  • 0 Henk 'm!

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 20:14

crisp

Devver

Pixelated

Dit wil je niet met reguliere expressies doen, hiervoor zal je moeten gaan tokenizen en het liefst een stack-based parser gebruiken...

Intentionally left blank


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Ik heb mijn ubb parser nu zo ingedeeld:

-haal alle [tag]'s op (ook end-tags).
- kijk of ze juist genest zijn en of ze wel zijn toegestaan op die locatie (zo niet negeer ze)
- render de tags (zet [ b]content[ /b] om in <b>content</b> etc.)

De laatste twee stappen werken al perfect, maar de het eerste gedeelte dus blijkbaar niet.

Zou ik dan gewoon met strpos etc. gaan zoeken naar tags en handmatig de params zoeken?

Acties:
  • 0 Henk 'm!

  • NMe
  • Registratie: Februari 2004
  • Laatst online: 09-09 13:58

NMe

Quia Ego Sic Dico.

'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Ik weet niet of ik stacked-based parsen helemaal begrijp (nooit iets over gelezen).
Maar ik heb het als volgt gedaan:

array $tags
- voeg alle tags toe aan $tags
- $tags += [tag=<tag> params=[p1, p2, ... pN] ]
- vervang [tag...] voor [{<id>}] (met id unique)

- ga na voor elke tag in $tags of deze hier is toegestaan en goed genest is

- zet de tags om in juist code en vervang de bijbehorende [{<id>}] voor dit resultaat

Dit alles heeft verschillende categorien waar tags in elkaar mogen en je kan per tag opgeven of deze intern geparsed moet worden of neit ([ code][ code][ /code][ /code] levert dan <code>[ code]</code>[ /code]

Ik weet niet of dit stacked-based is, maar lijkt mij wel ;)

Acties:
  • 0 Henk 'm!

  • NMe
  • Registratie: Februari 2004
  • Laatst online: 09-09 13:58

NMe

Quia Ego Sic Dico.

Zo'n parser werkt doorgaans iets anders dan je omschrijft. Stapsgewijs:
  1. Loop letter voor letter/woord voor woord (afhankelijk van welke precisie je nodig hebt) door de string heen.
  2. Kijk of je een teken tegenkomt dat de huidige "token" afbreekt en een nieuwe token begint.
  3. Plaats alles vanaf het einde van de vorige token tot aan dat nieuwe token in een string en stop die string in een array als het een starttoken is. Is het een eindtoken, dan haal je juist de laatste entry van die array/stack weg (LiFo).
  4. Terwijl je de stack opbouwt bouw je ook op basis daarvan je nieuwe string op. Kom je een [u]-token tegen, dan open je in je string een <u>. Komt je daarna tekst tegen, dan druk je die tekst letterlijk af in je string. Kom je een [/u] tegen en verwacht je die daar ook daadwerkelijk (wat je kan zien aan de laatste entry op je stack) dan druk je een </u> af.

'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Ik heb het opgelost maar niet op de manier die NMe voorstelde.

Ik heb de grote regex vervangen voor meerdere kleine, en nu werk hij wel naar behoren.

Namelijk preg_* heeft moeite met herhalende captures, dus (?:(a)b(c))* werkt niet goed, maar (a)b(c) in preg_match_all geeft wel het juiste resultaat. Ik heb het vinden van tags nu verdeelt in 4 verschillende categorieen (gevonden door regex-en):
code:
1
2
3
4
5
6
7
8
 - /end-tag (^/\w+$)
 - start-tag (^\w+$)
 - start-tag met params en tag als eerste param:
    + om het te vinden: #^(?:(?:(?<=^)|[ \t]+)\w+[ \t]*=[ \t]*(?:"[^"\n]*"|\'[^\'\n]*\'|[^ \n\]]*))*$#
    + om de params te pakken te krijgen: #(?:^|[ \t]+)(\w+)[ \t]*=[ \t]*(?:"([^"\n]*)"|\'([^\'\n]*)\'|([^ \n\]]*))#
- start-tag met params:
    + om het te vinden: #^(\w+)((?:[ \t]+\w+[ \t]*=[ \t]*(?:"[^"\n]*"|\'[^\'\n]*\'|[^ \n\]]*))*)$#
    + om de params te pakken te krijgen: #[ \t]+(\w+)[ \t]*=[ \t]*(?:"([^"\n]*)"|\'([^\'\n]*)\'|([^ \n\]]*))#


Op deze manier werkt wel alles naar behoren en kijk ik de opbouw van mijn systeem/parse het zelfde houden want de rest van de parser werkt wel precies zou het zou moeten (met zeer uitgebreide tag-rules).

Acties:
  • 0 Henk 'm!

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 20:14

crisp

Devver

Pixelated

Wellicht wil je ook nog rekening houden met escaped quotes in quoted attribuut waarden, dan krijg je een dergelijke expressie:
code:
1
(\w+|"(?:\\\.|[^"]+)*"|\'(?:\\\.|[^\']+)*\')


Het grootste nadeel van dergelijke OR'ed expressies is echter dat bij mallformed input je dusdanig veel backtracking kan krijgen dat PCRE uit z'n stackspace loopt (en PHP en je webserver onderuit haalt(!)).

Je ziet overigens zelf ook dat je voor de tag-matching en uiteindelijk voor het apart uitsplitsen van je attributen nagenoeg dezelfde expressie gebruikt wat natuurlijk een beetje zonde is. Met een tag-tokenizer kan je dat dubbele werk voorkomen (en is uiteindelijk vaak ook sneller).

Intentionally left blank


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Bedankt voor je escape regex.

En is inderdaad waar wat je zegt over tokenized parsers (denk ik). Misschien moet ik mijn getTags() en parseTags() vervangen voor tokenize().

Het werkt nu als volgt:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 - Je voegt alle rules toe
      + je zegt op welke tags hij moet letten
      + dus [ b]bold[ /b] --> <b>bold</b> en [ b] mag overal in staan
      +  je zegt dat [ h1] alleen in de root tag mag staan (dus buiten aliena's etc.)
      + of de tag een single-tag is
      + ...

 - getTags() haalt 'alle' tags op (dus ook niet gedefineerde)
      + zet deze tags in een lijst met:
              - tag
               - deze een open- of close-tag is
               - de params
               - de originele text
      + elke tag die hij vind krijg een uqine id waarna in de text deze tag word vervangen door [{_ID_}] Hierdoor kan je later makkelijk de tags vervangen zonder dat er zich fouten voor doen.

 - parseTags() haalt fouten weg
      + kijkt naar indent
      + vervangt niet gesloten tags voor de originele text
      + vervangt niet geopende tags voor de originele text
      + kijkt of de child in een tag geparse moeten worden
      + uit eindelijk haalt hij alle close tags weg (en vermeld bij de bijbehorende opentag de id van deze close tag)

 - renderTags() zet de tags om in door de rule gegeven html code
      + gaat achterstevoren door de tags heen en vervangt dmv. een processor de tags voor html code
      + achterstevoren is omdat je zo voorkomt dat er [{_ID_}]'s worden weggegooid doordat een processor niet de content gebruikt. (je gaat zo namelijk van de diepste tags naar de buitenste tags)


Je kan per tag aan de processor (Via de rule) verschillende dingen doen: je kan een functie opgeven die de tag moet processen, je kan html code opgeven met daarin de content (met verschillende modefiers zoals trim, nl2br etc.). Ook kan je params opvragen (bv. quote tag: <small>Quote: {param|quote}</small><quote>{content|trim}</quote> )

Dus vooral de getTags en parseTags zouden nog verbeterd kunnen worden.

Acties:
  • 0 Henk 'm!

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 20:14

crisp

Devver

Pixelated

Gebruik je ook nog een soort van tag-matching algorithme in het geval van unbalanced tags?

Intentionally left blank


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Wat bedoel je met unbalanced tags ?

edit:
vanaf hier


In iedergeval kijk ik over elke tag die geopend word ook word gesloten, zo niet, dan word deze open-tag weer vervangen voor de originele text (dus weer [b] bijv.).

Ook kijk of een close tag wel geopend is, zo niet, word deze ook weer vervangen voor de originele text.

[ Voor 81% gewijzigd door vdvleon op 11-06-2009 14:47 ]


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
crisp schreef op donderdag 11 juni 2009 @ 10:11:
Wellicht wil je ook nog rekening houden met escaped quotes in quoted attribuut waarden, dan krijg je een dergelijke expressie:
code:
1
(\w+|"(?:\\\.|[^"]+)*"|\'(?:\\\.|[^\']+)*\')


Het grootste nadeel van dergelijke OR'ed expressies is echter dat bij mallformed input je dusdanig veel backtracking kan krijgen dat PCRE uit z'n stackspace loopt (en PHP en je webserver onderuit haalt(!)).
Volgens mij kun je dat laatste hier voorkomen door +'jes goed neer te zetten. Dus:
code:
1
(\w++|"(?:\\\.|[^"])*+"|\'(?:\\\.|[^\'])*+\')


Edit: wellicht dat de langere variant nog iets efficienter is:
This feature can be extremely useful to give perl hints about where it shouldn't backtrack. For instance, the typical "match a double-quoted string" problem can be most efficiently performed when written as:
code:
1
/"(?:[^"\\]++|\\.)*+"/
Verder is het natuurlijk de vraag of er niet al meer dan genoeg ubb-parsers zijn ;)

[ Voor 20% gewijzigd door pedorus op 11-06-2009 15:06 ]

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 20:14

crisp

Devver

Pixelated

pedorus schreef op donderdag 11 juni 2009 @ 14:59:
[...]

Volgens mij kun je dat laatste hier voorkomen door +'jes goed neer te zetten. Dus:
code:
1
(\w++|"(?:\\\.|[^"])*+"|\'(?:\\\.|[^\'])*+\')


Edit: wellicht dat de langere variant nog iets efficienter is:

[...]
hmmz, ik wist niet dat PCRE in PHP ook possessive quantifiers ondersteunde :o
Verder is het natuurlijk de vraag of er niet al meer dan genoeg ubb-parsers zijn ;)
Echt goede ubb-parsers zijn toch schaars hoor ;)

Intentionally left blank


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Ik moet de quote escape nog toepassen. Maar idd, goede parsers zijn schaars!

Wat ik nu met mijn parser kan kon ik niet met de pecl bbcode module.

Acties:
  • 0 Henk 'm!

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 20:14

crisp

Devver

Pixelated

vdvleon schreef op donderdag 11 juni 2009 @ 14:43:
Wat bedoel je met unbalanced tags ?

edit:
vanaf hier


In iedergeval kijk ik over elke tag die geopend word ook word gesloten, zo niet, dan word deze open-tag weer vervangen voor de originele text (dus weer [b] bijv.).

Ook kijk of een close tag wel geopend is, zo niet, word deze ook weer vervangen voor de originele text.
Wat ik doe in mijn parser is vanuit de verzameling tokens eerst een tree bouwen. Pas als de gehele tree compleet is herserialiseer ik dat naar HTML (dmv callbacks voor specifieke tags, en regexp-replacements voor data). Voor unbalanced tags gebruik ik het adoption agency algorithme: als aan het eind van de tokenstream ik niet terug ben op het root-level van de tree dan ga ik 1 level naar beneden en herbouw vervolgens de hele tree vanaf dat punt weer, waarbij ik dus een open-tag negeer. Dat herbouwen van de tree bij het weglaten van een open-tag is noodzakelijk omdat dat het content-model kan veranderen (allow/deny rules voor specifieke tags).

Ik kan helaas geen code delen aangezien mijn parser deels in werktijd is geschreven (het is de parser die we gebruiken voor o.a. de frontpage reacties, het forum gebruikt een andere parser maar ook die heb ik al voor een groot deel verbouwd, vnl voor performance. Er zitten nog maar een paar functionele verschillen tussen beide parsers maar ik kan niet beslissen welke er nou eigenlijk uiteindelijk beter is :P)...

Intentionally left blank


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Wel interesant altijd, dit soort dingen. Een goede template parser is ook altijd wel een uitdaging. Ik maak eerst de rest van cms af, waarna ik misschien later mijn ubb parser ga optimalizeren. Het werkt namelijk nu al wel, maar het kan dus nog beter.

Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
pedorus schreef op donderdag 11 juni 2009 @ 14:59:
Volgens mij kun je dat laatste hier voorkomen door +'jes goed neer te zetten. Dus:
code:
1
(\w++|"(?:\\\.|[^"])*+"|\'(?:\\\.|[^\'])*+\')
Wat betekend \w++ hier precies?
Of kan je dat zien als (?:\w+)+, dus meerdere woorden achter elkaar?

edit:
vanaf hier gewijzigd


Hij kan nu ook unscapen:

Het onderstaande leverd nu <a href="http://www.google.com/?h="hoi"" title="sdgs&39;sdgd">test</a> op.
code:
1
[url href="http://www.google.com/?h=\"hoi\"" title=sdgs\'gdssdgsdg]test[/url]

[ Voor 27% gewijzigd door vdvleon op 12-06-2009 00:28 ]


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Oeps, deze post mag weg. Ik bekijk bericht aan het gebruiken om wat ubb tags te testen en klikte verkeerd lol.

[ Voor 149% gewijzigd door vdvleon op 12-06-2009 03:03 ]


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
vdvleon schreef op vrijdag 12 juni 2009 @ 00:11:
Wat betekend \w++ hier precies?
Of kan je dat zien als (?:\w+)+, dus meerdere woorden achter elkaar?
Nee, dat is zeker niet (?:\w+)+, want dat zou compleet onzinnig zijn. ++ is possessive (zoals + in default greedy is, en +? ungreedy is). Zie voor meer info de link naar perlre die ik eerder gaf, en de documentatie van pcre. In de PHP-documentatie lijkt het vreemd genoeg te ontbreken. Anders gezegd komt het er op neer dat backtrackingpunten binnen dit stukje niet worden aangemaakt, waardoor backtracking minder optreed. \w++\w zal daardoor nooit matchen, omdat de eerste \w++ alles al opsoupeert en geen backtracking toestaat. \w+?\w en \w+\w staan backtracking wel toe, en die zullen wel een match geven in bijvoorbeeld dit stukje tekst.
Hij kan nu ook unscapen:
Het leukste is dan natuurlijk om ook even te melden hoe je dat hebt gedaan. ;)

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • vdvleon
  • Registratie: Januari 2008
  • Laatst online: 08-06-2023
Bedankt voor je uitleg over \w++ etc.

Ja soort voor het ontbreken van de oplossing:P

code:
1
2
3
 - Regex voor params:  #[ \t]+(\w+)[ \t]*=[ \t]*(?:"((?:\\\.|[^"])*+)"|\'((?:\\\.|[^\'])*+)\'|([^ \n\]]*+))#
    + In het [tag <params>] gedeelte]
- Regex voor unscapen:  #\\\(.)# replace: \1


edit:
Vanaf hier gewijzigd
crisp schreef op donderdag 11 juni 2009 @ 23:00:
[...]

Wat ik doe in mijn parser is vanuit de verzameling tokens eerst een tree bouwen. Pas als de gehele tree compleet is herserialiseer ik dat naar HTML (dmv callbacks voor specifieke tags, en regexp-replacements voor data). Voor unbalanced tags gebruik ik het adoption agency algorithme: als aan het eind van de tokenstream ik niet terug ben op het root-level van de tree dan ga ik 1 level naar beneden en herbouw vervolgens de hele tree vanaf dat punt weer, waarbij ik dus een open-tag negeer. Dat herbouwen van de tree bij het weglaten van een open-tag is noodzakelijk omdat dat het content-model kan veranderen (allow/deny rules voor specifieke tags).
Ik snap het princiepe, maar het toepassen wil ook lukken! :D
Mijn parser werkt nu ook goed met unbalanced tags.

[ Voor 60% gewijzigd door vdvleon op 12-06-2009 14:01 ]

Pagina: 1