login  Naam:   Wachtwoord: 
Registreer je!
 Forum

Shuffelen van tekst in HTML (Opgelost)

Offline Jointjeff - 09/10/2014 12:37
Avatar van JointjeffHTML interesse Hallo iedereen, FangorN ,

Ik heb een HTML-code waarvan de tekst binnen de HTML tags geshuffled moet worden.

Met een preg_match_all gooi ik daarom alle HTML elementen in een array.
  1. preg_match_all( '/<([^\s>]+)(.*?)>((.*?)<\/\1>)?|(?<=^|>)(.+?)(?=$|<)/i', $content , $elements );


Vervolgens foreach ik door de elementen heen en gebruik ik de shuffle() functie om de woorden te shuffelen.

Echter zijn er een aantal elementen die niet geshuffled moeten worden. Bijvoorbeeld de img-tags, ul, ol, blockquote (inhoud weer wel).

In het kort:
Een HTML-code waarin alle woorden geshuffled moeten worden, maar de HTML-tags intact blijven.

Hoe zou ik dit het beste aan kunnen pakken?

9 antwoorden

Gesponsorde links
Offline Thomas - 09/10/2014 13:25
Avatar van Thomas Moderator Euh, wat wil je hier uiteindelijk mee bereiken? 

Je wilt dus met bovenstaande regexp de woorden van $elements[4] door elkaar gooien? En dan dus per tag?

Stel je hebt een HTML string

<p>A B C</p>
<p>D E F</p>

Dan mag dit dus worden:

<p>B C A</p>
<p>E D F</p>

Maar niet dat A, B of C naar de tweede paragraaf gaat, en/of D, E of F naar de eerste? Shufflen geschiedt dus per tag?

Ik zou een soort van whitelist voor de te shufflen tags opstellen denk ik. Als al deze tags bestaan uit elementen die je niet nest, dan is het simpel. En anders, niet bepaald . Wat je waarschijnlijk gedwongen bent om te doen is het onthouden van de posities van de tags en de onderlinge samenhang, waarschijnlijk moet je dan al aan een soort van parser gaan denken... maar de oplossing hangt sterk af van wat je uiteindelijk wilt bereiken.
Offline Jointjeff - 09/10/2014 13:36 (laatste wijziging 09/10/2014 14:00)
Avatar van Jointjeff HTML interesse Ik probeer een techniek na te maken die ook te zien is op MyJour. Zij zetten de tekst in een shuffle totdat je bent ingelogd.

https://myjour....m-van-isis

Je voorbeeld is correct. Maar het volgende:
<li>A B C</li>
<li>D E F</li>

Moet dus ook dit worden:
<li>B A C</li>
<li>F E D</li>

Terwijl b.v. img-tags moeten worden overgeslagen, of beter: HTML onaangetast moet blijven.

Code die ik nu heb:
Plaatscode: 142381

Maar volgens mij moet dat beter kunnen.
Offline Thomas - 09/10/2014 14:08
Avatar van Thomas Moderator Lol what the hell. Wat is daar dan het achterliggende idee van? Is dit om zoekmachines toch relevante resultaten te laten vinden qua zoektermen in (semi afgeschermde) content?

Trouwens, als ik die pagina refresh staat de content in dezelfde geshufflede volgorde. Deze wordt dus waarschijnlijk gecached wat mij eigenlijk wel een verstandig idee lijkt, je wilt dit niet elke keer opnieuw uitrekenen maar opslaan in een aparte tabelkolom bij het origineel ofzo.

Bij gebrek aan een beter idee zou ik denken aan het bouwen van een soort van parser. Deze leest je content, markeert de positie van alle HTML-tags, deelt dit op in passages (mogelijk met recursie indien je geneste tags hebt). Dan moet je dus per passage bijhouden uit hoeveel woorden deze bestaat, vervolgens gooi je alle woorden in een bak. Na afloop van dit alles gebruik je de eerder opgebouwde structuur om een nieuwe tekst met gelijke structuur en gehusselde inhoud op te bouwen ofzo... Dat is best veel handwerk als je het mij vraagt. En HTML ontleden is best een hels karwei (waar ik overigens niet al te veel ervaring mee heb, ik gaf het meestal halverwege op lol). Heb je al alternatieven overwogen?
Offline Jointjeff - 09/10/2014 15:38
Avatar van Jointjeff HTML interesse Ja dat is het achterliggende idee inderdaad.

Ik sla nu ook de geshuffelde tekst op in de database, dus dan voorkom je inderdaad dat je telkens die hele functie weer moet uitvoeren.

Hoe zou jij dat aanpakken met die parser, technisch gezien. Ik ben al vrij trots dat ik het tot zover werkend heb gekregen, alleen, voor m'n gevoel kan het beter.

Ik zou niet direct een alternatief weten als ik eerlijk ben. Jij?
Offline Thomas - 09/10/2014 16:38 (laatste wijziging 09/10/2014 16:40)
Avatar van Thomas Moderator Als je het simpel wilt houden zou je er voor kunnen kiezen om enkel een inleidende paragraaf te tonen (in de juiste volgorde). Bijkomend voordeel daarvan is dat als dit opgepikt wordt door een zoekmachine dat deze passage bestaat uit een of meer leesbare zinnen. Ik weet sowieso niet of zoekmachines altijd de volledige text verwerken, wellicht kan onze SEO guru hier meer over vertellen? Daarnaast weet ik niet in hoeverre meta tags (description, keywords) nog relevant zijn?

Andere oplossingen die mij te binnen schoten (gebruik jQuery om text te husselen, controleer user agent en besluit daarna hoe je content toont, maar dan begeef je je al snel op glad ijs, dat neigt naar "cloaking" (ik dacht dat het zo heette, iig: je biedt dan een zoekmachine andere content aan dan een gewone bezoeker, in het ongunstigste geval kun je dan worden geblacklist)) zijn door handige gebruikers makkelijk te omzeilen (zet javascript uit, pretendeer een zoekmachine te zijn).

@parser: ik heb hier zogauw geen goed technische oplossing voor; het idee is dat je je tekst opsplitst in HTML (je tags) en content. De structuur van je HTML moet behouden worden en je content moet volgens bepaalde regels door elkaar gehusseld worden. Je moet je complete HTML-tekst dus opdelen in blokjes ("tokens") met een bepaalde betekenis.
EDIT: misschien kan een XML library (SimpleXML, XPath, ... of hoe het ook heet?) hierbij uitkomst bieden?

Wat je misschien nog zou kunnen doen om eea eenvoudiger te maken is de nesting eruit kieperen, in de zin dat je geneste tags stript, maar daarmee help je je tekst om zeep in termen van zoekmachine optimalisatie. Denk bijvoorbeeld aan het volgende:

<p>Dit is een lopende zin met een <em>belangrijke passage</em>.</p>

Als die <em> eruitvliegt, dan is je tekst al in waarde afgenomen waarschijnlijk. En als je dit niet zou doen staat er wellicht complete brol binnen die <em>...</em> tags.

Dat doe je eigenlijk sowieso al: je rukt de oorspronkelijke (con)text hiermee uit zijn verband. Als je er zo tegenaan kijkt is dat hele door elkaar gooien van woorden niet echt zo'n strak plan.

Ik zou gaan voor een simpele(re) oplossing denk ik.
Offline Jointjeff - 09/10/2014 16:46
Avatar van Jointjeff HTML interesse Ik heb al eens een test gedaan met een artikel die geshuffeled was en deze werd vrij goed geïndexeerd (beter dan ik had gedacht).

Andere content tonen aan de user agent van b.v. Google is inderdaad cloaking, vandaar ook deze oplossing. Ook niet netjes misschien, maar niet tegen de regels.

Ben nu bezig met 'PHP Simple HTML DOM Parser Manual'. Gaat al heel aardig tot dusver.

Bedankt voor de info. Ik zal de vorderingen hier ook plaatsen.
Offline Thomas - 09/10/2014 17:56
Avatar van Thomas Moderator Dit is zo gauw wat ik in de loop van deze thread in elkaar heb gezet. Dit is nog niet echt bruikbaar, maar wellicht geeft het wat inspiratie:

  1. <?php
  2. $content = '<h1>Hello world</h1>
  3. <p>This is a paragraph text.</p>
  4. <ul class="test" style="">
  5. <li>This</li>
  6. <li>is</li>
  7. <li>an</li>
  8. <li>unordered<ul>
  9. <li>nested</li>
  10. </ul></li>
  11. <li>list.</li>
  12. </ul>
  13. <p>This is yet another paragraph. With an image <img src="" alt="" /></p>';
  14.  
  15. class HtmlTokens
  16. {
  17. protected $input;
  18. protected $structure;
  19.  
  20. public function __construct($input) {
  21. $this->input = $input;
  22. $this->structure = array();
  23. $this->openPositions = array();
  24. $this->closePositions = array();
  25. }
  26.  
  27. public function parse() {
  28. $stack = array(); // structure index => tag type
  29. $matches = array();
  30. $structureIndex = 0;
  31.  
  32. preg_match_all('#<([^>]+)>#si', $this->input, $matches, PREG_OFFSET_CAPTURE);
  33. // use $matches[0] for positions and $matches[1] to read tag types
  34. foreach ($matches[0] as $matchKey => $matchData) {
  35. $tagContent = explode(' ', trim($matches[1][$matchKey][0]));
  36. $tagPositionStart = $matchData[1];
  37. $tagPositionEnd = $matchData[1] + strlen($matchData[0]) - 1;
  38. $tag = $tagContent[0];
  39. $matchingIndex = false; // structure index of matching opening tag
  40. $parent = false;
  41.  
  42. if ($tag{0} == '/') {
  43. // closing tag
  44. $tag = substr($tag, 1);
  45. $type = 'closing';
  46. // there should be something on the stack, otherwise: malformed HTML?
  47. if (count($stack) == 0) {
  48. die('malformed HTML?');
  49. }
  50. $stackData = array_pop($stack);
  51. // the tags should match as well
  52. if ($stackData['tag'] == $tag) {
  53. $matchingIndex = $stackData['index'];
  54. // also update the opening tag
  55. $this->structure[$stackData['index']]['match'] = $structureIndex;
  56. } else {
  57. die('malformed HTML?');
  58. }
  59. } else {
  60. if (count($stack) > 0) {
  61. $parent = $stack[count($stack)-1]['index'];
  62. }
  63. // opening tag... or self closing tag
  64. if (array_pop($tagContent) == '/') {
  65. $type = 'self closing';
  66. } else {
  67. $type = 'opening';
  68. $stack[] = array(
  69. 'index' => $structureIndex,
  70. 'tag' => $tag,
  71. );
  72. }
  73. }
  74. $this->structure[$structureIndex] = array(
  75. 'tag' => $tag,
  76. 'type' => $type,
  77. 'start' => $tagPositionStart,
  78. 'end' => $tagPositionEnd,
  79. 'match' => $matchingIndex,
  80. 'parent' => ($type == 'opening' || $type == 'self closing' ? $parent : false),
  81. );
  82. $structureIndex++;
  83. } // foreach
  84.  
  85. // if the stack still contains unclosed tags...
  86. if (count($stack)) {
  87. die('malformed HTML?');
  88. }
  89.  
  90. var_dump($this->structure);
  91. return $this;
  92. } // parse
  93. } // class
  94.  
  95. $test = new HtmlTokens($content);
  96. $test->parse();
  97. ?>
Offline Jointjeff - 10/10/2014 20:06 (laatste wijziging 10/10/2014 20:11)
Avatar van Jointjeff HTML interesse Ik heb met de PHP Simple HTML DOM Parser een script geschreven die in grote lijnen dat wat ik wil, zie ook: http://plaatscode.be/142383/

Zit enkel nog met tabellen. Die zijn wat lastiger door de "nested"-elementen.

Ik zal jouw methode ook even gaan uitproberen.
Offline Thomas - 11/10/2014 12:58
Avatar van Thomas Moderator Oh, ik dacht dat je de woorden van de hele tekst door de hele tekst wilde husselen, met uitzondering van enkele elementen, maar wat in bovenstaande code gebeurt -als ik het goed begrijp- is dat de inhoud van specifieke elementen binnen het element worden verwisseld. Zoveel vermoedde ik al eerder:
FangorN schreef:
Maar niet dat A, B of C naar de tweede paragraaf gaat, en/of D, E of F naar de eerste? Shufflen geschiedt dus per tag?
Wat is er trouwens mis met implode en explode (je gebruikt een preg_replace en een loop hiervoor)?

Anyway, mijn laatste versie:
  1. <?php
  2. class HtmlTokens
  3. {
  4. protected $input;
  5. protected $structure;
  6.  
  7. public function __construct($input) {
  8. $this->input = $input;
  9. $this->structure = array(
  10. 'tag' => 'root',
  11. 'type' => 'root',
  12. 'start' => 0,
  13. 'end' => 0,
  14. 'match' => false,
  15. 'parent' => false,
  16. 'children' => array(),
  17. ),
  18. );
  19. $this->openPositions = array();
  20. $this->closePositions = array();
  21. }
  22.  
  23. public function parse() {
  24. $stack = array(); // structure index => tag type
  25. $matches = array();
  26. $structureIndex = 1;
  27. // http://stackoverflow.com/questions/3558119/are-self-closing-tags-valid-in-html5
  28. $voidElements = array(
  29. 'area',
  30. 'base',
  31. 'br',
  32. 'col',
  33. 'command',
  34. 'embed',
  35. 'hr',
  36. 'img',
  37. 'input',
  38. 'keygen',
  39. 'link',
  40. 'meta',
  41. 'param',
  42. 'source',
  43. 'track',
  44. 'wbr',
  45. );
  46.  
  47. preg_match_all('#<([^>]+)>#si', $this->input, $matches, PREG_OFFSET_CAPTURE);
  48. // use $matches[0] for positions and $matches[1] to read tag types
  49. foreach ($matches[0] as $matchKey => $matchData) {
  50. $tagContent = explode(' ', trim($matches[1][$matchKey][0]));
  51. $tagPositionStart = $matchData[1];
  52. $tagPositionEnd = $matchData[1] + strlen($matchData[0]) - 1;
  53. $tag = $tagContent[0];
  54. $matchingIndex = false; // structure index of matching opening tag
  55. $parent = false;
  56.  
  57. if ($tag{0} == '/') {
  58. // closing tag
  59. $tag = substr($tag, 1);
  60. $type = 'closing';
  61. // there should be something on the stack, otherwise: malformed HTML?
  62. if (count($stack) == 0) {
  63. die('malformed HTML?');
  64. }
  65. $stackData = array_pop($stack);
  66. // the tags should match as well
  67. if ($stackData['tag'] == $tag) {
  68. $matchingIndex = $stackData['index'];
  69. // also update the opening tag
  70. $this->structure[$stackData['index']]['match'] = $structureIndex;
  71. } else {
  72. die('malformed HTML?');
  73. }
  74. } else {
  75. if (count($stack) > 0) {
  76. $parent = $stack[count($stack)-1]['index'];
  77. } else {
  78. $parent = 0;
  79. }
  80.  
  81. // opening tag... or self closing tag - the slash is optional apparently
  82. if (array_pop($tagContent) == '/') {
  83. if (!in_array($tag, $voidElements)) {
  84. die('a non-void element may not be self-closing');
  85. }
  86. $type = 'self closing';
  87. } elseif (in_array($tag, $voidElements)) {
  88. $type = 'self closing';
  89. } else {
  90. $type = 'opening';
  91. $stack[] = array(
  92. 'index' => $structureIndex,
  93. 'tag' => $tag,
  94. );
  95. }
  96. }
  97. $this->structure[$structureIndex] = array(
  98. 'tag' => $tag,
  99. 'type' => $type,
  100. 'start' => $tagPositionStart,
  101. 'end' => $tagPositionEnd,
  102. 'match' => $matchingIndex,
  103. 'parent' => ($type == 'opening' || $type == 'self closing' ? $parent : false),
  104. 'children' => array(),
  105. );
  106. // store item as child of parent
  107. if ($type == 'opening' || $type == 'self closing') {
  108. $this->structure[$parent]['children'][] = $structureIndex;
  109. }
  110. $structureIndex++;
  111. } // foreach
  112.  
  113. // if the stack still contains unclosed tags...
  114. if (count($stack)) {
  115. die('malformed HTML?');
  116. }
  117.  
  118. // var_dump($this->structure);
  119. return $this;
  120. } // parse
  121.  
  122. // $ignoreTags contains tags in which the order of words should not be altered
  123. public function getNonTagIntervals($ignoreTags=array()) {
  124. $positions = array();
  125. $inputLength = strlen($this->input) - 1;
  126.  
  127. $index = 0;
  128. $positions[$index][0] = 0;
  129. $positions[$index][2] = false; // initially, we do not ignore text
  130. $ignoreStack = array();
  131.  
  132. foreach ($this->structure as $data) {
  133. // here you could add rules that determine whether content should be shuffled inside specific tags, or not
  134. // are we opening a tag?
  135. if ($data['type'] == 'opening') {
  136. // is it an element we want to ignore?
  137. if (in_array($data['tag'], $ignoreTags)) {
  138. // add it to the stack
  139. $ignoreStack[] = $data['tag'];
  140. }
  141. } elseif ($data['type'] == 'closing') {
  142. // if it is an element we want to ignore, it implicitly matches the opening tag (parse() guarantees this)
  143. // @todo double check this :)
  144. if (in_array($data['tag'], $ignoreTags)) {
  145. array_pop($ignoreStack);
  146. }
  147. }
  148. $positions[$index][1] = ($data['start'] - 1 < 0 ? 0 : $data['start']) ;
  149. $index++;
  150. $positions[$index][0] = ($data['end'] + 1 > $inputLength ? $inputLength : $data['end'] + 1);
  151. $positions[$index][2] = count($ignoreStack) > 0; // whether to ignore
  152. }
  153. $positions[$index][1] = $inputLength;
  154.  
  155. // traverse $positions in reverse order
  156. $test = $this->input;
  157. foreach (array_reverse($positions) as $position) {
  158. // only process nonempty intervals
  159. if ($position[1] > $position[0]) {
  160. $length = $position[1] - $position[0];
  161. $text = substr($test, $position[0], $length);
  162. // do not handle text if it contains nothing but spaces and linebreaks
  163. if (trim($text) != '') {
  164. // ignore?
  165. if ($position[2] === false) {
  166. // debug to see what parts are selected
  167. // $text = '[c]'.$text.'[/c]';
  168. $text = explode(' ', trim($text));
  169. shuffle($text);
  170. $text = implode(' ', $text);
  171. }
  172. }
  173. $test = substr($test, 0, $position[0]).$text.substr($test, $position[1]);
  174. }
  175. }
  176. return $test;
  177. }
  178. } // class
  179.  
  180. $content = '<h1>Hello world</h1>
  181. <p>This is a paragraph text.</p>
  182. <ul class="test" style="">
  183. <li>This</li>
  184. <li>is</li>
  185. <li>an</li>
  186. <li>unordered<ul>
  187. <li>nested</li>
  188. </ul></li>
  189. <li>list.</li>
  190. </ul>
  191. <p>This is yet another paragraph. With an image <img src="" alt="" /></p>';
  192.  
  193. $test = new HtmlTokens($content);
  194. $test->parse();
  195. // Aan getNonTagIntervals() kun je een array meegeven van HTML elementen die je niet wilt shufflen.
  196. echo '<pre>'.htmlspecialchars($test->getNonTagIntervals(), ENT_QUOTES, 'UTF-8').'</pre>';
  197. ?>
Bedankt door: Jointjeff
Gesponsorde links
Je moet ingelogd zijn om een reactie te kunnen posten.
Actieve forumberichten
© 2002-2024 Sitemasters.be - Regels - Laadtijd: 0.228s