From 14f481bcf6b0338882b7a0ff858ed9ced1c6328b Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Sun, 23 Jul 2006 00:11:03 +0000 Subject: [PATCH] svn:eol-style = native git-svn-id: http://htmlpurifier.org/svnroot/htmlpurifier/trunk@97 48356398-32a2-884e-a903-53898d9a118a --- benchmarks/Lexer.php | 286 +++--- benchmarks/samples/Lexer/1.html | 104 +- benchmarks/samples/Lexer/2.html | 32 +- benchmarks/samples/Lexer/3.html | 256 ++--- benchmarks/samples/Lexer/4.html | 1080 ++++++++++---------- docs/security.txt | 70 +- docs/spec.txt | 712 ++++++------- library/HTMLPurifier.php | 54 +- library/HTMLPurifier/AttrDef.php | 20 +- library/HTMLPurifier/ChildDef.php | 336 +++--- library/HTMLPurifier/Definition.php | 888 ++++++++-------- library/HTMLPurifier/Generator.php | 88 +- library/HTMLPurifier/Lexer/DirectLex.php | 706 ++++++------- library/HTMLPurifier/Lexer/PEARSax3.php | 114 +-- library/HTMLPurifier/Token.php | 118 +-- tests/HTMLPurifier/ChildDefTest.php | 262 ++--- tests/HTMLPurifier/DefinitionTest.php | 532 +++++----- tests/HTMLPurifier/GeneratorTest.php | 176 ++-- tests/HTMLPurifier/Lexer/DirectLexTest.php | 164 +-- tests/HTMLPurifier/LexerTest.php | 392 +++---- tests/index.php | 48 +- 21 files changed, 3219 insertions(+), 3219 deletions(-) diff --git a/benchmarks/Lexer.php b/benchmarks/Lexer.php index a1c2a424..fdd01863 100644 --- a/benchmarks/Lexer.php +++ b/benchmarks/Lexer.php @@ -1,144 +1,144 @@ - new HTMLPurifier_Lexer_DirectLex(), - 'PEARSax3' => new HTMLPurifier_Lexer_PEARSax3() -); - -if (version_compare(PHP_VERSION, '5', '>=')) { - require_once 'HTMLPurifier/Lexer/DOMLex.php'; - $LEXERS['DOMLex'] = new HTMLPurifier_Lexer_DOMLex(); -} - -// PEAR -require_once 'Benchmark/Timer.php'; // to do the timing -require_once 'Text/Password.php'; // for generating random input - -// custom class to aid unit testing -class RowTimer extends Benchmark_Timer -{ - - var $name; - - function RowTimer($name, $auto = false) { - $this->name = htmlentities($name); - $this->Benchmark_Timer($auto); - } - - function getOutput() { - - $total = $this->TimeElapsed(); - $result = $this->getProfiling(); - $dashes = ''; - - $out = ''; - - $out .= "{$this->name}"; - - foreach ($result as $k => $v) { - if ($v['name'] == 'Start' || $v['name'] == 'Stop') continue; - - //$perc = (($v['diff'] * 100) / $total); - //$tperc = (($v['total'] * 100) / $total); - - $out .= '' . $v['diff'] . ''; - - //$out .= '' . number_format($perc, 2, '.', '') . - // '%'; - - } - - $out .= ''; - - return $out; - } -} - -function print_lexers() { - global $LEXERS; - $first = true; - foreach ($LEXERS as $key => $value) { - if (!$first) echo ' / '; - echo htmlspecialchars($key); - $first = false; - } -} - -function do_benchmark($name, $document) { - global $LEXERS; - - $timer = new RowTimer($name); - $timer->start(); - - foreach($LEXERS as $key => $lexer) { - $tokens = $lexer->tokenizeHTML($document); - $timer->setMarker($key); - } - - $timer->stop(); - $timer->display(); -} - -?> - - -Benchmark: <?php print_lexers(); ?> - - -

Benchmark:

- - $value) { - echo ''; -} -?> -'; -$snippets[] = ''; - -foreach ($snippets as $snippet) { - do_benchmark($snippet, $snippet); -} - -// random input - -$random = Text_Password::create(80, 'unpronounceable', 'qwerty <>="\''); - -do_benchmark('Random input', $random); - -?>
Case' . htmlspecialchars($key) . '
- -Random input was: ' . - '' . - htmlspecialchars($random) . ''; - -?> - - + new HTMLPurifier_Lexer_DirectLex(), + 'PEARSax3' => new HTMLPurifier_Lexer_PEARSax3() +); + +if (version_compare(PHP_VERSION, '5', '>=')) { + require_once 'HTMLPurifier/Lexer/DOMLex.php'; + $LEXERS['DOMLex'] = new HTMLPurifier_Lexer_DOMLex(); +} + +// PEAR +require_once 'Benchmark/Timer.php'; // to do the timing +require_once 'Text/Password.php'; // for generating random input + +// custom class to aid unit testing +class RowTimer extends Benchmark_Timer +{ + + var $name; + + function RowTimer($name, $auto = false) { + $this->name = htmlentities($name); + $this->Benchmark_Timer($auto); + } + + function getOutput() { + + $total = $this->TimeElapsed(); + $result = $this->getProfiling(); + $dashes = ''; + + $out = ''; + + $out .= "{$this->name}"; + + foreach ($result as $k => $v) { + if ($v['name'] == 'Start' || $v['name'] == 'Stop') continue; + + //$perc = (($v['diff'] * 100) / $total); + //$tperc = (($v['total'] * 100) / $total); + + $out .= '' . $v['diff'] . ''; + + //$out .= '' . number_format($perc, 2, '.', '') . + // '%'; + + } + + $out .= ''; + + return $out; + } +} + +function print_lexers() { + global $LEXERS; + $first = true; + foreach ($LEXERS as $key => $value) { + if (!$first) echo ' / '; + echo htmlspecialchars($key); + $first = false; + } +} + +function do_benchmark($name, $document) { + global $LEXERS; + + $timer = new RowTimer($name); + $timer->start(); + + foreach($LEXERS as $key => $lexer) { + $tokens = $lexer->tokenizeHTML($document); + $timer->setMarker($key); + } + + $timer->stop(); + $timer->display(); +} + +?> + + +Benchmark: <?php print_lexers(); ?> + + +

Benchmark:

+ + $value) { + echo ''; +} +?> +'; +$snippets[] = ''; + +foreach ($snippets as $snippet) { + do_benchmark($snippet, $snippet); +} + +// random input + +$random = Text_Password::create(80, 'unpronounceable', 'qwerty <>="\''); + +do_benchmark('Random input', $random); + +?>
Case' . htmlspecialchars($key) . '
+ +Random input was: ' . + '' . + htmlspecialchars($random) . ''; + +?> + + \ No newline at end of file diff --git a/benchmarks/samples/Lexer/1.html b/benchmarks/samples/Lexer/1.html index 60a61450..43130c01 100644 --- a/benchmarks/samples/Lexer/1.html +++ b/benchmarks/samples/Lexer/1.html @@ -1,53 +1,53 @@ - - - - Main Page - Huaxia Taiji Club - - - - - - -
中文
- -
Huaxia Taiji Club - 华夏太极俱乐部
- -
-

Main Page

Taiji (Tai Chi)

- - - - - -

Taiji is an ancient Chinese tradition of movement systems that is associated with philosophy, physiology, psychology, geometry and dynamics. It is the slowest form of martial arts and is meant to improve the internal spirit. It is soothing to the soul and extremely invigorating.

- -

The founder of Taiji was Zhang Sanfeng (Chang San-feng), who was a monk of the Wu Dang (Wu Tang) Monastery and lived in the period from 1391 to 1459. His exercises stressed suppleness and elasticity as opposed to the hardness and force of other martial art styles. Several centuries old, Taiji was originally developed as a form of self-defense, emphasizing strength, balance, flexibility and speed. Tai Chi also differs from other martial arts in that it is based on the Taoist religion and aims to avoid aggressive forces.

- -

Modern Taiji includes many forms — Quan, Sword and Fan. Impacting the mind and body of the practitioners, Taiji is practiced as a meditative exercise made up of a series of forms, or choreographed motions, requiring slow, gentle movement of the arms, legs and torso. Taiji practitioners learn to center their attention on their breathing and body movements so that the exercise strengthens their overall mental and physical awareness. In a sense, Taiji is similar to yoga in that it is also a form of moving meditation, with the goal of achieving stillness through the motion and awareness of breath. To perform Taiji, practitioners have to empty their mind of thoughts and worries in order to achieve harmony. It is a great aid for reducing stress and improving the quality of life.

- -

In China and in communities all over the world, Taiji is practiced by young and old in the early morning hours. It's a great way to bring a new and fresh day!

- -

Check out our gallery.

- -
- -
Click on photo to see HR version
- + + + + Main Page - Huaxia Taiji Club + + + + + + +
中文
+ +
Huaxia Taiji Club + 华夏太极俱乐部
+ +
+

Main Page

Taiji (Tai Chi)

+ + + + + +

Taiji is an ancient Chinese tradition of movement systems that is associated with philosophy, physiology, psychology, geometry and dynamics. It is the slowest form of martial arts and is meant to improve the internal spirit. It is soothing to the soul and extremely invigorating.

+ +

The founder of Taiji was Zhang Sanfeng (Chang San-feng), who was a monk of the Wu Dang (Wu Tang) Monastery and lived in the period from 1391 to 1459. His exercises stressed suppleness and elasticity as opposed to the hardness and force of other martial art styles. Several centuries old, Taiji was originally developed as a form of self-defense, emphasizing strength, balance, flexibility and speed. Tai Chi also differs from other martial arts in that it is based on the Taoist religion and aims to avoid aggressive forces.

+ +

Modern Taiji includes many forms — Quan, Sword and Fan. Impacting the mind and body of the practitioners, Taiji is practiced as a meditative exercise made up of a series of forms, or choreographed motions, requiring slow, gentle movement of the arms, legs and torso. Taiji practitioners learn to center their attention on their breathing and body movements so that the exercise strengthens their overall mental and physical awareness. In a sense, Taiji is similar to yoga in that it is also a form of moving meditation, with the goal of achieving stillness through the motion and awareness of breath. To perform Taiji, practitioners have to empty their mind of thoughts and worries in order to achieve harmony. It is a great aid for reducing stress and improving the quality of life.

+ +

In China and in communities all over the world, Taiji is practiced by young and old in the early morning hours. It's a great way to bring a new and fresh day!

+ +

Check out our gallery.

+ +
+ +
Click on photo to see HR version
+ \ No newline at end of file diff --git a/benchmarks/samples/Lexer/2.html b/benchmarks/samples/Lexer/2.html index a39352cf..0d78f49b 100644 --- a/benchmarks/samples/Lexer/2.html +++ b/benchmarks/samples/Lexer/2.html @@ -1,17 +1,17 @@ -Google - -
edwardzyang@gmail.com | Personalized Home | Search History | My Account | Sign out
Google

-
+
edwardzyang@gmail.com | Personalized Home | Search History | My Account | Sign out
Google

+
Web    Images    Groups    News    Froogle    Local    more »
 
  Advanced Search
  Preferences
  Language Tools


Advertising Programs - Business Solutions - About Google

©2006 Google

\ No newline at end of file diff --git a/benchmarks/samples/Lexer/3.html b/benchmarks/samples/Lexer/3.html index 776708d4..c76c5cfb 100644 --- a/benchmarks/samples/Lexer/3.html +++ b/benchmarks/samples/Lexer/3.html @@ -1,128 +1,128 @@ - - -Anime Digi-Lib Index - - - -
- -
- - - - - - - - - - - - - - - - « - - Previous | - Top 100 | - Next - - - » - - - - -
- - - - - - - - - - -
 Search:The WebAngelfire    Planet
-
- Edit your Site show site directoryBrowse Sites hosted by angelfire
  - Vonagehosted by angelfire
-
-
- - -
- - - -
- - - - - -
-

May 1, 2000

-

Pop Culture

-

by. H. Finkelstein

- -
-

Welcome to the Anime Digi-Lib, a virtual index to anime on the - internet. This site strives to house a comprehensive index to both personal - and commercial websites and provides reviews to these sites. We hope to - be a gateway for people who've never imagined they'd ever be interested - in Japanese Animation.

- - - - - - -
-

 

-

 

- -
- - - - - - - - - - - - - - - - -
Search term:
Case-sensitive - -yes
exactfuzzy
-
- - -
- - - - - -
What is better, subtitled or dubbed anime?
Subtitled
Current results
Free - Web Polls
- -
- - - + + +Anime Digi-Lib Index + + + +
+ +
+ + + + + + + + + + + + + + + + « + + Previous | + Top 100 | + Next + + + » + + + + +
+ + + + + + + + + + +
 Search:The WebAngelfire    Planet
+
+ Edit your Site show site directoryBrowse Sites hosted by angelfire
  + Vonagehosted by angelfire
+
+
+ + +
+ + + +
+ + + + + +
+

May 1, 2000

+

Pop Culture

+

by. H. Finkelstein

+ +
+

Welcome to the Anime Digi-Lib, a virtual index to anime on the + internet. This site strives to house a comprehensive index to both personal + and commercial websites and provides reviews to these sites. We hope to + be a gateway for people who've never imagined they'd ever be interested + in Japanese Animation.

+ + + + + + +
+

 

+

 

+ +
+ + + + + + + + + + + + + + + + +
Search term:
Case-sensitive - +yes
exactfuzzy
+
+ + +
+ + + + + +
What is better, subtitled or dubbed anime?
Subtitled
Current results
Free + Web Polls
+ +
+ + + diff --git a/benchmarks/samples/Lexer/4.html b/benchmarks/samples/Lexer/4.html index a3ac4c86..095a1d35 100644 --- a/benchmarks/samples/Lexer/4.html +++ b/benchmarks/samples/Lexer/4.html @@ -1,540 +1,540 @@ - - - - - - - - Tai Chi Chuan - Wikipedia, the free encyclopedia - - - - - - - - - - - - - - - - - -
-
-
- - -
Registration for Wikimania 2006 is open.   
-

Tai Chi Chuan

-
-

From Wikipedia, the free encyclopedia

- -
-
Jump to: navigation, search
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
???
- -
-
-
Yang Chengfu in a posture from the Tai Chi solo form known as Single Whip, circa 1918 -
-
Enlarge
-Yang Chengfu in a posture from the Tai Chi solo form known as Single Whip, circa 1918
-
-
-
-
Chinese Name
Hanyu PinyinTàijíquán
Wade-GilesT'ai4 Chi2 Ch'üan2
Simplified Chinese???
Traditional Chinese???
Cantonesetaai3 gik6 kyun4
Japanese Hiragana???????
Korean???
VietnameseThái C?c Quy?n
-

Tai Chi Chuan, T'ai Chi Ch'üan or Taijiquan (Traditional Chinese: ???; Simplified Chinese: ???; pinyin: Tàijíquán; literally "supreme ultimate fist"), commonly known as Tai Chi, T'ai Chi, or Taiji, is an internal Chinese martial art. There are different styles of T'ai Chi Ch'üan, although most agree they are all based on the system originally taught by the Chen family to the Yang family starting in 1820. It is often promoted and practiced as a martial arts therapy for the purposes of health and longevity, (some recent medical studies support its effectiveness). T'ai Chi Ch'üan is considered a soft style martial art, an art applied with as complete a relaxation or "softness" in the musculature as possible, to distinguish its theory and application from that of the hard martial art styles which use a degree of tension in the muscles.

- -

Variations of T'ai Chi Ch'üan's basic training forms are well known as the slow motion routines that groups of people practice every morning in parks across China and other parts of the world. Traditional T'ai Chi training is intended to teach awareness of one's own balance and what affects it, awareness of the same in others, an appreciation of the practical value in one's ability to moderate extremes of behavior and attitude at both mental and physical levels, and how this applies to effective self-defense principles.

- - - - -
-
-

Contents

-
- -
-

- - -

-

Overview

-

Historically, T'ai Chi Ch'üan has been regarded as a martial art, and its traditional practitioners still teach it as one. Even so, it has developed a worldwide following among many thousands of people with little or no interest in martial training for its aforementioned benefits to health and health maintenance. Some call it a form of moving meditation, and T'ai Chi theory and practice evolved in agreement with many of the principles of traditional Chinese medicine. Besides general health benefits and stress management attributed to beginning and intermediate level T'ai Chi training, many therapeutic interventions along the lines of traditional Chinese medicine are taught to advanced T'ai Chi students.

-

T'ai Chi Ch'üan as physical training is characterized by its requirement for the use of leverage through the joints based on coordination in relaxation rather than muscular tension in order to neutralize or initiate physical attacks. The slow, repetitive work involved in that process is said to gently increase and open the internal circulation (breath, body heat, blood, lymph, peristalsis, etc.). Over time, proponents say, this enhancement becomes a lasting effect, a direct reversal of the constricting physical effects of stress on the human body. This reversal allows much more of the students' native energy to be available to them, which they may then apply more effectively to the rest of their lives; families, careers, spiritual or creative pursuits, hobbies, etc.

- -

The study of T'ai Chi Ch'üan involves three primary subjects:

-
    -
  • Health - an unhealthy or otherwise uncomfortable person will find it difficult to meditate to a state of calmness or to use T'ai Chi as a martial art. T'ai Chi's health training therefore concentrates on relieving the physical effects of stress on the body and mind.
  • -
  • Meditation - the focus meditation and subsequent calmness cultivated by the meditative aspect of T'ai Chi is seen as necessary to maintain optimum health (in the sense of effectively maintaining stress relief or homeostasis) and in order to use it as a soft style martial art.
  • -
  • Martial art - the ability to competently use T'ai Chi as a martial art is said to be proof that the health and meditation aspects are working according to the dictates of the theory of T'ai Chi Ch'üan.
  • - -
-

In its traditional form (many modern variations exist which ignore at least one of the above requirements) every aspect of its training has to conform with all three of the aforementioned categories.

-

The Mandarin term "T'ai Chi Ch'üan" translates as "Supreme Ultimate Boxing" or "Boundless Fist". T'ai Chi training involves learning solo routines, known as forms, and two person routines, known as pushing hands, as well as acupressure-related manipulations taught by traditional schools. T'ai Chi Ch'üan is seen by many of its schools as a variety of Taoism, and it does seemingly incorporate many Taoist principles into its practice (see below). It is an art form said to date back many centuries (although not reliably documented under that name before 1850), with precursor disciplines dating back thousands of years. The explanation given by the traditional T'ai Chi family schools for why so many of their previous generations have dedicated their lives to the study and preservation of the art is that the discipline it seems to give its students to dramatically improve the effects of stress in their lives, with a few years of hard work, should hold a useful purpose for people living in a stressful world. They say that once the T'ai Chi principles have been understood and internalized into the bodily framework the practitioner will have an immediately accessible "toolkit" thereby to improve and then maintain their health, to provide a meditative focus, and that can work as an effective and subtle martial art for self-defense.

-

Teachers say the study of T'ai Chi Ch'üan is, more than anything else, about challenging one's ability to change oneself appropriately in response to outside forces. These principles are taught using the examples of physics as experienced by two (or more) bodies in combat. In order to be able to protect oneself or someone else by using change, it is necessary to understand what the consequences are of changing appropriately, changing inappropriately and not changing at all in response to an attack. Students, by this theory, will appreciate the full benefits of the entire art in the fastest way through physical training of the martial art aspect.

- -

Wu Chien-ch'üan, co-founder of the Wu family style, described the name T'ai Chi Ch'üan this way at the beginning of the 20th century:

-
-
"Various people have offered different explanations for the name T'ai Chi Ch'uan. Some have said: 'In terms of self-cultivation, one must train from a state of movement towards a state of stillness. T'ai Chi comes about through the balance of yin and yang. In terms of the art of attack and defense then, in the context of the changes of full and empty, one is constantly internally latent, not outwardly expressive, as if the yin and yang of T'ai Chi have not yet divided apart.'
- -
Others say: 'Every movement of T'ai Chi Ch'uan is based on circles, just like the shape of a T'ai Chi symbol. Therefore, it is called T'ai Chi Ch'uan.' Both explanations are quite reasonable, especially the second, which is more complete."
-
- -

- -

Training and techniques

-
-
The T'ai Chi Symbol or T'ai Chi T'u (Taijitu) -
-
Enlarge
-The T'ai Chi Symbol or T'ai Chi T'u (Taijitu)
-
-
-

As the name T'ai Chi Ch'üan is held to be derived from the T'ai Chi symbol, the taijitu or t'ai chi t'u (???, pinyin tàijítú), commonly known in the West as the "yin-yang" diagram, T'ai Chi Ch'üan techniques are said therefore to physically and energetically balance yin (receptive) and yang (active) principles: "From ultimate softness comes ultimate hardness."

- -

The core training involves two primary features: the first being the solo form or ch'üan, a slow sequence of movements which emphasize a straight spine, relaxed breathing and a natural range of motion; the second being different styles of pushing hands or t'ui shou (??) for training "stickiness" and sensitivity in the reflexes through various motions from the forms in concert with a training partner in order to learn leverage, timing, coordination and positioning when interacting with another. Pushing hands is seen as necessary not only for training the self-defense skills of a soft style such as T'ai Chi by demonstrating the forms' movement principles experientially, but also it is said to improve upon the level of conditioning provided by practice of the solo forms by increasing the workload on students while they practice those movement principles.

-

The solo form should take the students through a complete, natural, range of motion over their centre of gravity. Accurate, repeated practice of the solo routine is said to retrain posture, encourage circulation throughout the students' bodies, maintain flexibility through their joints and further familiarize students with the martial application sequences implied by the forms. The major traditional styles of T'ai Chi have forms which differ somewhat cosmetically, but there are also many obvious similarities which point to their common origin. The solo forms, empty-hand and weapon, are catalogues of movements that are practised individually in pushing hands and martial application scenarios to prepare students for self-defence training. In most traditional schools different variations of the solo forms can be practiced; fast/slow, small circle/large circle, square/round (which are different expressions of leverage through the joints), low sitting/high sitting (the degree to which weight-bearing knees are kept bent throughout the form), for example.

-

In a fight, if one uses hardness to resist violent force then both sides are certain to be injured, at least to some degree. Such injury, according to T'ai Chi theory, is a natural consequence of meeting brute force with brute force. The collision of two like forces, yang with yang, is known as "double-weighted" in T'ai Chi terminology. Instead, students are taught not to fight or resist an incoming force, but to meet it in softness and "stick" to it, following its motion while remaining in physical contact until the incoming force of attack exhausts itself or can be safely redirected, the result of meeting yang with yin. Done correctly, achieving this yin/yang or yang/yin balance in combat (and, by extension, other areas of one's life) is known as being "single-weighted" and is a primary goal of T'ai Chi Ch'üan training. Lao Tzu provided the archetype for this in the Tao Te Ching when he wrote, "The soft and the pliable will defeat the hard and strong." This soft "neutralization" of an attack can be accomplished very quickly in an actual fight by an adept practitioner. A T'ai Chi student has to be well conditioned by many years of disciplined training; stable, sensitive and elastic mentally and physically in order to realize this ability, however.

- -

Other training exercises include:

-
    -
  • Weapons training and fencing applications employing the straight sword known as the jian or chien or gim (jiàn ?), a heavier curved sabre, sometimes called a broadsword or tao (dao ?, which is actually considered a big knife), folding fan, staff (?), 7 foot (2 m) spear and 13 foot (4 m) lance (both called qiang ?). More exotic weapons still used by some traditional styles are the large Da Dao or Ta Tao (??) sabre, halberd (ji ?), cane, rope-dart, three sectional staff, lasso, whip, chain whip and steel whip.
  • - -
  • Two-person tournament sparring (as part of push hands competitions and/or san shou ??);
  • -
  • Breathing exercises; nei kung (?? nèigong) or, more commonly, ch'i kung (?? qìgong) to develop ch'i (? qì) or "breath energy" in coordination with physical movement and post standing or combinations of the two. These were formerly taught only to disciples as a separate, complementary training system. In the last 50 years they have become more well known to the general public.
  • - -
-

T'ai Chi's martial aspect relies on sensitivity to the opponent's movements and centre of gravity dictating appropriate responses. Effectively affecting or "capturing" the opponent's centre of gravity immediately upon contact is trained as the primary goal of the martial T'ai Chi student, and from there all other technique can follow with seeming effortlessness. The alert calmness required to achieve the necessary sensitivity is acquired over thousands of hours of first yin (slow, repetitive, meditative, low impact) and then later adding yang ("realistic," active, fast, high impact) martial training; forms, pushing hands and sparring. T'ai Chi Ch'üan trains in three basic ranges, close, medium and long, and then everything in between. Pushes and open hand strikes are more common than punches, and kicks are usually to the legs and lower torso, never higher than the hip in most styles. The fingers, fists, palms, sides of the hands, wrists, forearms, elbows, shoulders, back, hips, knees and feet are commonly used to strike, with strikes to the eyes, throat, heart, groin and other acupressure points trained by advanced students. There is an extensive repertoire of joint traps, locks and breaks (chin na), particularly applied to lock up or break an opponent's elbows, wrists, fingers, ankles, back or neck. Most T'ai Chi teachers expect their students to thoroughly learn defensive or neutralizing skills first, and a student will have to demonstrate proficiency with them before offensive skills will be extensively trained. There is also an emphasis in the traditional schools on kind-heartedness. One is expected to show mercy to one's opponents, as instanced by a poem preserved in some of the T'ai Chi families said to be derived from the Shaolin temple:

-
-
"I would rather maim than kill
- -
Hurt than maim
-
Intimidate than hurt
-
Avoid than intimidate."
-
-
-
An outdoor Chen style class in Beijing -
-
Enlarge
-An outdoor Chen style class in Beijing
-
-
- - -

-

Styles and history

-

There are five major styles of T'ai Chi Ch'üan, each named after the Chinese family that teaches (or taught) it:

- -

The order of seniority is as listed above. The order of popularity is Yang, Wu, Chen, Sun, and Wu/Hao. The first five major family styles share much underlying theory, but differ in their approaches to training.

-

In the modern world there are now dozens of new styles, hybrid styles and offshoots of the main styles, but the five family schools are the groups recognised by the international community as being orthodox. For example, there are several groups teaching what they call Wu Tang style T'ai Chi Ch'üan (??????). The best known modern style going by the name Wu Tang has gained some publicity internationally, especially in the UK and Europe, but was originally taught by a senior student of the Wu (?) style.

- -

The designation Wu Tang Ch'üan is also used to broadly distinguish internal or nei chia martial arts (said to be a specialty of the monasteries at Wu Tang Shan) from what are known as the external or wei chia styles based on Shaolinquan kung fu, although that distinction is sometimes disputed by individual schools. In this broad sense, among many T'ai Chi schools all styles of T'ai Chi (as well as related arts such as Pa Kua Chang and Hsing-i Ch'üan) are therefore considered to be "Wu Tang style" martial arts. The schools that designate themselves "Wu Tang style" relative to the family styles mentioned above mostly claim to teach an "original style" they say was formulated by a Taoist monk called Zhang Sanfeng and taught by him in the Taoist monasteries at Wu Tang Shan. Some consider that what is practised under that name today may be a modern back-formation based on stories and popular veneration of Zhang Sanfeng (see below) as well as the martial fame of the Wu Tang monastery (there are many other martial art styles historically associated with Wu Tang besides T'ai Chi).

- -

When tracing T'ai Chi Ch'üan's formative influences to Taoist and Buddhist monasteries, one has little more to go on than legendary tales from a modern historical perspective, but T'ai Chi Ch'üan's practical connection to and dependence upon the theories of Sung dynasty Neo-Confucianism (a conscious synthesis of Taoist, Buddhist and Confucian traditions, esp. the teachings of Mencius) is readily apparent to its practitioners. The philosophical and political landscape of that time in Chinese history is fairly well documented, even if the origin of the art later to become known as T'ai Chi Ch'üan in it is not. T'ai Chi Ch'üan's theories and practice are therefore believed by some schools to have been formulated by the Taoist monk Zhang Sanfeng in the 12th century, a time frame fitting well with when the principles of the Neo-Confucian school were making themselves felt in Chinese intellectual life. Therefore the didactic story is told that Zhang Sanfeng as a young man studied Tao Yin (??, Pinyin daoyin) breathing exercises from his Taoist teachers and martial arts at the Buddhist Shaolin monastery, eventually combining the martial forms and breathing exercises to formulate the soft or internal principles we associate with T'ai Chi Ch'üan and related martial arts. Its subsequent fame attributed to his teaching, Wu Tang monastery was known thereafter as an important martial center for many centuries, its many styles of internal kung fu preserved and refined at various Taoist temples.

- - -

-

Family tree

-

This family tree is not comprehensive.

-
-LEGENDARY FIGURES
-   |
-Zhang Sanfeng*
-circa 12th century
-NEI CHIA
-
-   |
-Wang Zongyue*
-T'AI CHI CH'ÜAN
-   |
-THE 5 MAJOR CLASSICAL FAMILY STYLES
-   |
-Chen Wangting
-1600-1680 9th generation Chen
-CHEN STYLE
-   |
-   +-------------------------------------------------------------------+
-   |                                                                   |
-Chen Changxing                                                     Chen Youben
-1771-1853 14th generation Chen                                     circa 1800s 14th generation Chen
-Chen Old Frame                                                     Chen New Frame
-   |                                                                   |
-
-Yang Lu-ch'an                                                      Chen Qingping
-1799-1872                                                          1795-1868
-YANG STYLE                                                         Chen Small Frame, Zhao Bao Frame
-   |                                                                   |
-   +---------------------------------+-----------------------------+   |
-   |                                 |                             |   |
-Yang Pan-hou                      Yang Chien-hou                   Wu Yu-hsiang
-1837-1892                         1839-1917                        1812-1880
-Yang Small Frame                     |                             WU/HAO STYLE
-
-   |                                 +-----------------+                      |
-   |                                 |                 |                      |
-Wu Ch'uan-yü                      Yang Shao-hou     Yang Ch'eng-fu          Li I-yü
-1834-1902                         1862-1930         1883-1936               1832-1892
-   |                              Yang Small Frame  Yang Big Frame            |
-Wu Chien-ch'üan                                        |                    Hao Wei-chen
-
-1870-1942                                           Yang Shou-chung         1849-1920
-WU STYLE                                            1910-1985                 |
-108 Form                                                                      |
-   |                                                                        Sun Lu-t'ang
-Wu Kung-i                                                                   1861-1932
-1900-1970                                                                   SUN STYLE
-
-   |                                                                          |
-Wu Ta-kuei                                                                  Sun Hsing-i
-1923-1970                                                                   1891-1929                  
-                           
-MODERN FORMS        
-                                      
-from Yang Ch`eng-fu                     
-        |               
-        |              
-        |                         
-        +--------------+      
-        |              |       
-  Cheng Man-ch'ing     |   
-  1901-1975            |    
-  Short (37) Form      |     
-                       |
-              Chinese Sports Commission
-              1956
-              Beijing 24 Form
-              .
-              .
-              1989
-              42 Competition Form
-
-              (Wushu competition form combined from Sun, Wu, Chen, and Yang styles)
-
- -

-

Notes to Family tree table

-

Names denoted by an asterisk are legendary or semilegendary figures in the lineage, which means their involvement in the lineage, while accepted by most of the major schools, isn't independently verifiable from known historical records.

-

The Cheng Man-ch'ing and Chinese Sports Commission short forms are said to be derived from Yang family forms, but neither are recognized as Yang family T'ai Chi Ch'üan by current Yang family teachers. The Chen, Yang and Wu families are now promoting their own shortened demonstration forms for competitive purposes.

- - -

-

Modern T'ai Chi

-
-
Yang style in Shanghai -
-
Enlarge
-Yang style in Shanghai
-
-
-

T'ai Chi has become very popular in the last twenty years or so, as the baby boomers age and T'ai Chi's reputation for ameliorating the effects of aging becomes more well-known. Hospitals, clinics, community and senior centers are all hosting T'ai Chi classes in communities around the world. As a result of this popularity, there has been some divergence between those who say they practice T'ai Chi primarily for fighting, those who practice it for its aesthetic appeal (as in the shortened, modern, theatrical "Taijiquan" forms of wushu, see below), and those who are more interested in its benefits to physical and mental health. The wushu aspect is primarily for show; the forms taught for those purposes are designed to earn points in competition and are mostly unconcerned with either health maintenance or martial ability. More traditional stylists still see the two aspects of health and martial arts as equally necessary pieces of the puzzle, the yin and yang of T'ai Chi Ch'üan. The T'ai Chi "family" schools therefore still present their teachings in a martial art context even though the majority of their students nowadays profess that they are primarily interested in training for the claimed health benefits.

- -

Along with Yoga, it is one of the fastest growing fitness and health maintenance activities, in terms of numbers of students enrolling in classes. Since there is no universal certification process and most Westerners haven't seen very much T'ai Chi and don't know what to look for, practically anyone can learn or even make up a few moves and call themselves a teacher. This is especially prevalent in the New Age community. Relatively few of these teachers even know that there are martial applications to the T'ai Chi forms. Those who do know that it is a martial art usually don't teach martially themselves. If they do teach self-defense, it is often a mixture of motions which the teachers think look like T'ai Chi Ch'üan with some other system. This is especially evident in schools located outside of China. While this phenomenon may have made some external aspects of T'ai Chi available for a wider audience, the traditional T'ai Chi family schools see the martial focus as a fundamental part of their training, both for health and self-defense purposes. They claim that while the students may not need to practice martial applications themselves to derive a benefit from T'ai Chi training, they assert that T'ai Chi teachers at least should know the martial applications to ensure that the movements they teach are done correctly and safely by their students. Also, working on the ability to protect oneself from physical attack (one of the most stressful things that can happen to a person) certainly falls under the category of complete "health maintenance." For these reasons they claim that a school not teaching those aspects somewhere in their syllabus cannot be said to be actually teaching the art itself, and will be much less likely to be able to reproduce the full health benefits that made T'ai Chi's reputation in the first place.

- -

-

Modern forms

- -
-
Women practicing non-martial T'ai Chi in Chinatown (New York City, New York, USA). -
-
Enlarge
-Women practicing non-martial T'ai Chi in Chinatown (New York City, New York, USA).
-
-
- -

In order to standardize T'ai Chi Ch'üan for wushu tournament judging, and because many of the family T'ai Chi Ch'üan teachers had either moved out of China or had been forced to stop teaching after the Communist regime was established in 1949, the government sponsored Chinese Sports Committee brought together four of their wushu teachers to truncate the Yang family hand form to 24 postures in 1956. They wanted to somehow retain the look of T'ai Chi Ch'üan but make an easy to remember routine that was less difficult to teach and much less difficult to learn than longer (generally 88 to 108 posture) classical solo hand forms. In 1976, they developed a slightly longer form also for the purposes of demonstration that still didn't involve the complete memory, balance and coordination requirements of the traditional forms. This was a combination form, the Combined 48 Forms that were created by three wushu coaches headed by Professor Men Hui Feng. The combined forms were created based on simplifying and combining some features of the classical forms from four of the original styles; Ch'en, Yang, Wu, and Sun. Even though shorter modern forms don't have the conditioning benefits of the classical forms, the idea was to take what they felt were distinctive cosmetic features of these styles and to express them in a shorter time for purposes of competition.

- -

As T'ai Chi again became popular on the Mainland, competitive forms were developed to be completed within a 6 minute time limit. In the late 1980s, the Chinese Sports Committee standardized many different competition forms. It had developed sets said to represent the four major styles as well as combined forms. These five sets of forms were created by different teams, and later approved by a committee of wushu coaches in China. All sets of forms thus created were named after their style, e.g., the Ch'en Style National Competition Form is the 56 Forms, and so on. The combined forms are The 42 Form or simply the Competition Form, as it is known in China. In the 11th Asian Games of 1990, wushu was included as an item for competition for the first time with the 42 Form being chosen to represent T'ai Chi. The International Wushu Federation (IWUF) has applied for wushu to be part of the Olympic games. If accepted, it is likely that T'ai Chi and wushu will be represented as demonstration events in 2008.

- -

Representatives of the original T'ai Chi families do not teach the forms developed by the Chinese Sports Committee. T'ai Chi Ch'üan has historically been seen by them as a martial art, not a sport, with competitions mostly entered as a hobby or to promote one's school publicly, but with little bearing on measuring actual accomplishment in the art. Their criticisms of modern forms include that the modern, "government" routines have no standardized, internally consistent training requirements. Also, that people studying competition forms rarely train pushing hands or other power generation trainings vital to learning the martial applications of T'ai Chi Ch'üan and thereby lack the quality control traditional teachers maintain is essential for achieving the full benefits from both the health and the martial aspect of traditional T'ai Chi training.

- -

-

Health benefits

-

Researchers have found that long-term T'ai Chi practice had favorable effects on the promotion of balance control, flexibility and cardiovascular fitness and reduced the risk of falls in elders. The studies also reported reduced pain, stress and anxiety in healthy subjects. Other studies have indicated improved cardiovascular and respiratory function in healthy subjects as well as those who had undergone coronary artery bypass surgery. Patients also benefited from T'ai Chi who suffered from heart failure, high blood pressure, heart attacks, arthritis and multiple sclerosis.

-

T'ai Chi has also been shown to reduce the symptoms of young Attention Deficit and Hyperactivity Disorder (ADHD) sufferers. T'ai Chi's gentle, low impact, movements surprisingly burn more calories than surfing and nearly as many as downhill skiing. T'ai Chi also boosts aspects of the immune system's function very significantly, and has been shown to reduce the incidence of anxiety, depression, and overall mood disturbance. (See research citations listed below.)

- -

A pilot study has found evidence that T'ai Chi and related qigong helps reduce the severity of diabetes.[1]

- -

-

Citations to medical research

-
    -
  • Wolf SL, Sattin RW, Kutner M. Intense T'ai Chi exercise training and fall occurrences in older, transitionally frail adults: a randomized, controlled trial. J Am Geriatr Soc. 2003 Dec; 51(12): 1693-701. PMID 14687346
  • -
  • Wang C, Collet JP, Lau J. The effect of Tai Chi on health outcomes in patients with chronic conditions: a systematic review. Arch Intern Med. 2004 Mar 8;164(5):493-501. PMID 15006825
  • - -
  • Search a listing of articles relating to the FICSIT trials and T'ai Chi [2]
  • -
  • Hernandez-Reif, M., Field, T.M., & Thimas, E. (2001). Attention deficit hyperactivity disorder: benefits from Tai Chi. Journal of Bodywork & Movement Therapies, 5(2):120-3, 2001 Apr, 5(23 ref), 120-123
  • -
  • Calorie Burning Chart [3]
  • -
  • Tai Chi boosts T-Cell counts in immune system [4]
  • -
  • Tai Chi, depression, anxiety, and mood disturbance (American Psychological Association) Journal of Psychosomatic Research, 1989 Vol 33 (2) 197-206
  • - -
  • A comprehensive listing of Tai Chi medical research links [5]
  • -
  • References to medical publications [6]
  • -
  • Tai Chi a promising remedy for diabetes, Australian Broadcasting Corporation, 20 December, 2005 - Pilot study of Qigong and tai chi in diabetes sufferers.
  • -
  • Health Research Articles on "Tai Chi as Health Therapy" for many issues, i.e. ADHD, Cardiac Health & Rehabilitation, Diabetes, High Blood Pressure, Menopause, Bone Loss, Weight Loss, etc.[7]
  • -
- - -

-

See also

- - -

-

External links

- - - - - - - -
-
-
-
-
-
-
Views
- -
-
-
Personal tools
- -
- - - - - - -
-
In other languages
- -
- -
-
- - -
- - - + + + + + + + + Tai Chi Chuan - Wikipedia, the free encyclopedia + + + + + + + + + + + + + + + + + +
+
+
+ + +
Registration for Wikimania 2006 is open.   
+

Tai Chi Chuan

+
+

From Wikipedia, the free encyclopedia

+ +
+
Jump to: navigation, search
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
???
+ +
+
+
Yang Chengfu in a posture from the Tai Chi solo form known as Single Whip, circa 1918 +
+
Enlarge
+Yang Chengfu in a posture from the Tai Chi solo form known as Single Whip, circa 1918
+
+
+
+
Chinese Name
Hanyu PinyinTàijíquán
Wade-GilesT'ai4 Chi2 Ch'üan2
Simplified Chinese???
Traditional Chinese???
Cantonesetaai3 gik6 kyun4
Japanese Hiragana???????
Korean???
VietnameseThái C?c Quy?n
+

Tai Chi Chuan, T'ai Chi Ch'üan or Taijiquan (Traditional Chinese: ???; Simplified Chinese: ???; pinyin: Tàijíquán; literally "supreme ultimate fist"), commonly known as Tai Chi, T'ai Chi, or Taiji, is an internal Chinese martial art. There are different styles of T'ai Chi Ch'üan, although most agree they are all based on the system originally taught by the Chen family to the Yang family starting in 1820. It is often promoted and practiced as a martial arts therapy for the purposes of health and longevity, (some recent medical studies support its effectiveness). T'ai Chi Ch'üan is considered a soft style martial art, an art applied with as complete a relaxation or "softness" in the musculature as possible, to distinguish its theory and application from that of the hard martial art styles which use a degree of tension in the muscles.

+ +

Variations of T'ai Chi Ch'üan's basic training forms are well known as the slow motion routines that groups of people practice every morning in parks across China and other parts of the world. Traditional T'ai Chi training is intended to teach awareness of one's own balance and what affects it, awareness of the same in others, an appreciation of the practical value in one's ability to moderate extremes of behavior and attitude at both mental and physical levels, and how this applies to effective self-defense principles.

+ + + + +
+
+

Contents

+
+ +
+

+ + +

+

Overview

+

Historically, T'ai Chi Ch'üan has been regarded as a martial art, and its traditional practitioners still teach it as one. Even so, it has developed a worldwide following among many thousands of people with little or no interest in martial training for its aforementioned benefits to health and health maintenance. Some call it a form of moving meditation, and T'ai Chi theory and practice evolved in agreement with many of the principles of traditional Chinese medicine. Besides general health benefits and stress management attributed to beginning and intermediate level T'ai Chi training, many therapeutic interventions along the lines of traditional Chinese medicine are taught to advanced T'ai Chi students.

+

T'ai Chi Ch'üan as physical training is characterized by its requirement for the use of leverage through the joints based on coordination in relaxation rather than muscular tension in order to neutralize or initiate physical attacks. The slow, repetitive work involved in that process is said to gently increase and open the internal circulation (breath, body heat, blood, lymph, peristalsis, etc.). Over time, proponents say, this enhancement becomes a lasting effect, a direct reversal of the constricting physical effects of stress on the human body. This reversal allows much more of the students' native energy to be available to them, which they may then apply more effectively to the rest of their lives; families, careers, spiritual or creative pursuits, hobbies, etc.

+ +

The study of T'ai Chi Ch'üan involves three primary subjects:

+
    +
  • Health - an unhealthy or otherwise uncomfortable person will find it difficult to meditate to a state of calmness or to use T'ai Chi as a martial art. T'ai Chi's health training therefore concentrates on relieving the physical effects of stress on the body and mind.
  • +
  • Meditation - the focus meditation and subsequent calmness cultivated by the meditative aspect of T'ai Chi is seen as necessary to maintain optimum health (in the sense of effectively maintaining stress relief or homeostasis) and in order to use it as a soft style martial art.
  • +
  • Martial art - the ability to competently use T'ai Chi as a martial art is said to be proof that the health and meditation aspects are working according to the dictates of the theory of T'ai Chi Ch'üan.
  • + +
+

In its traditional form (many modern variations exist which ignore at least one of the above requirements) every aspect of its training has to conform with all three of the aforementioned categories.

+

The Mandarin term "T'ai Chi Ch'üan" translates as "Supreme Ultimate Boxing" or "Boundless Fist". T'ai Chi training involves learning solo routines, known as forms, and two person routines, known as pushing hands, as well as acupressure-related manipulations taught by traditional schools. T'ai Chi Ch'üan is seen by many of its schools as a variety of Taoism, and it does seemingly incorporate many Taoist principles into its practice (see below). It is an art form said to date back many centuries (although not reliably documented under that name before 1850), with precursor disciplines dating back thousands of years. The explanation given by the traditional T'ai Chi family schools for why so many of their previous generations have dedicated their lives to the study and preservation of the art is that the discipline it seems to give its students to dramatically improve the effects of stress in their lives, with a few years of hard work, should hold a useful purpose for people living in a stressful world. They say that once the T'ai Chi principles have been understood and internalized into the bodily framework the practitioner will have an immediately accessible "toolkit" thereby to improve and then maintain their health, to provide a meditative focus, and that can work as an effective and subtle martial art for self-defense.

+

Teachers say the study of T'ai Chi Ch'üan is, more than anything else, about challenging one's ability to change oneself appropriately in response to outside forces. These principles are taught using the examples of physics as experienced by two (or more) bodies in combat. In order to be able to protect oneself or someone else by using change, it is necessary to understand what the consequences are of changing appropriately, changing inappropriately and not changing at all in response to an attack. Students, by this theory, will appreciate the full benefits of the entire art in the fastest way through physical training of the martial art aspect.

+ +

Wu Chien-ch'üan, co-founder of the Wu family style, described the name T'ai Chi Ch'üan this way at the beginning of the 20th century:

+
+
"Various people have offered different explanations for the name T'ai Chi Ch'uan. Some have said: 'In terms of self-cultivation, one must train from a state of movement towards a state of stillness. T'ai Chi comes about through the balance of yin and yang. In terms of the art of attack and defense then, in the context of the changes of full and empty, one is constantly internally latent, not outwardly expressive, as if the yin and yang of T'ai Chi have not yet divided apart.'
+ +
Others say: 'Every movement of T'ai Chi Ch'uan is based on circles, just like the shape of a T'ai Chi symbol. Therefore, it is called T'ai Chi Ch'uan.' Both explanations are quite reasonable, especially the second, which is more complete."
+
+ +

+ +

Training and techniques

+
+
The T'ai Chi Symbol or T'ai Chi T'u (Taijitu) +
+
Enlarge
+The T'ai Chi Symbol or T'ai Chi T'u (Taijitu)
+
+
+

As the name T'ai Chi Ch'üan is held to be derived from the T'ai Chi symbol, the taijitu or t'ai chi t'u (???, pinyin tàijítú), commonly known in the West as the "yin-yang" diagram, T'ai Chi Ch'üan techniques are said therefore to physically and energetically balance yin (receptive) and yang (active) principles: "From ultimate softness comes ultimate hardness."

+ +

The core training involves two primary features: the first being the solo form or ch'üan, a slow sequence of movements which emphasize a straight spine, relaxed breathing and a natural range of motion; the second being different styles of pushing hands or t'ui shou (??) for training "stickiness" and sensitivity in the reflexes through various motions from the forms in concert with a training partner in order to learn leverage, timing, coordination and positioning when interacting with another. Pushing hands is seen as necessary not only for training the self-defense skills of a soft style such as T'ai Chi by demonstrating the forms' movement principles experientially, but also it is said to improve upon the level of conditioning provided by practice of the solo forms by increasing the workload on students while they practice those movement principles.

+

The solo form should take the students through a complete, natural, range of motion over their centre of gravity. Accurate, repeated practice of the solo routine is said to retrain posture, encourage circulation throughout the students' bodies, maintain flexibility through their joints and further familiarize students with the martial application sequences implied by the forms. The major traditional styles of T'ai Chi have forms which differ somewhat cosmetically, but there are also many obvious similarities which point to their common origin. The solo forms, empty-hand and weapon, are catalogues of movements that are practised individually in pushing hands and martial application scenarios to prepare students for self-defence training. In most traditional schools different variations of the solo forms can be practiced; fast/slow, small circle/large circle, square/round (which are different expressions of leverage through the joints), low sitting/high sitting (the degree to which weight-bearing knees are kept bent throughout the form), for example.

+

In a fight, if one uses hardness to resist violent force then both sides are certain to be injured, at least to some degree. Such injury, according to T'ai Chi theory, is a natural consequence of meeting brute force with brute force. The collision of two like forces, yang with yang, is known as "double-weighted" in T'ai Chi terminology. Instead, students are taught not to fight or resist an incoming force, but to meet it in softness and "stick" to it, following its motion while remaining in physical contact until the incoming force of attack exhausts itself or can be safely redirected, the result of meeting yang with yin. Done correctly, achieving this yin/yang or yang/yin balance in combat (and, by extension, other areas of one's life) is known as being "single-weighted" and is a primary goal of T'ai Chi Ch'üan training. Lao Tzu provided the archetype for this in the Tao Te Ching when he wrote, "The soft and the pliable will defeat the hard and strong." This soft "neutralization" of an attack can be accomplished very quickly in an actual fight by an adept practitioner. A T'ai Chi student has to be well conditioned by many years of disciplined training; stable, sensitive and elastic mentally and physically in order to realize this ability, however.

+ +

Other training exercises include:

+
    +
  • Weapons training and fencing applications employing the straight sword known as the jian or chien or gim (jiàn ?), a heavier curved sabre, sometimes called a broadsword or tao (dao ?, which is actually considered a big knife), folding fan, staff (?), 7 foot (2 m) spear and 13 foot (4 m) lance (both called qiang ?). More exotic weapons still used by some traditional styles are the large Da Dao or Ta Tao (??) sabre, halberd (ji ?), cane, rope-dart, three sectional staff, lasso, whip, chain whip and steel whip.
  • + +
  • Two-person tournament sparring (as part of push hands competitions and/or san shou ??);
  • +
  • Breathing exercises; nei kung (?? nèigong) or, more commonly, ch'i kung (?? qìgong) to develop ch'i (? qì) or "breath energy" in coordination with physical movement and post standing or combinations of the two. These were formerly taught only to disciples as a separate, complementary training system. In the last 50 years they have become more well known to the general public.
  • + +
+

T'ai Chi's martial aspect relies on sensitivity to the opponent's movements and centre of gravity dictating appropriate responses. Effectively affecting or "capturing" the opponent's centre of gravity immediately upon contact is trained as the primary goal of the martial T'ai Chi student, and from there all other technique can follow with seeming effortlessness. The alert calmness required to achieve the necessary sensitivity is acquired over thousands of hours of first yin (slow, repetitive, meditative, low impact) and then later adding yang ("realistic," active, fast, high impact) martial training; forms, pushing hands and sparring. T'ai Chi Ch'üan trains in three basic ranges, close, medium and long, and then everything in between. Pushes and open hand strikes are more common than punches, and kicks are usually to the legs and lower torso, never higher than the hip in most styles. The fingers, fists, palms, sides of the hands, wrists, forearms, elbows, shoulders, back, hips, knees and feet are commonly used to strike, with strikes to the eyes, throat, heart, groin and other acupressure points trained by advanced students. There is an extensive repertoire of joint traps, locks and breaks (chin na), particularly applied to lock up or break an opponent's elbows, wrists, fingers, ankles, back or neck. Most T'ai Chi teachers expect their students to thoroughly learn defensive or neutralizing skills first, and a student will have to demonstrate proficiency with them before offensive skills will be extensively trained. There is also an emphasis in the traditional schools on kind-heartedness. One is expected to show mercy to one's opponents, as instanced by a poem preserved in some of the T'ai Chi families said to be derived from the Shaolin temple:

+
+
"I would rather maim than kill
+ +
Hurt than maim
+
Intimidate than hurt
+
Avoid than intimidate."
+
+
+
An outdoor Chen style class in Beijing +
+
Enlarge
+An outdoor Chen style class in Beijing
+
+
+ + +

+

Styles and history

+

There are five major styles of T'ai Chi Ch'üan, each named after the Chinese family that teaches (or taught) it:

+ +

The order of seniority is as listed above. The order of popularity is Yang, Wu, Chen, Sun, and Wu/Hao. The first five major family styles share much underlying theory, but differ in their approaches to training.

+

In the modern world there are now dozens of new styles, hybrid styles and offshoots of the main styles, but the five family schools are the groups recognised by the international community as being orthodox. For example, there are several groups teaching what they call Wu Tang style T'ai Chi Ch'üan (??????). The best known modern style going by the name Wu Tang has gained some publicity internationally, especially in the UK and Europe, but was originally taught by a senior student of the Wu (?) style.

+ +

The designation Wu Tang Ch'üan is also used to broadly distinguish internal or nei chia martial arts (said to be a specialty of the monasteries at Wu Tang Shan) from what are known as the external or wei chia styles based on Shaolinquan kung fu, although that distinction is sometimes disputed by individual schools. In this broad sense, among many T'ai Chi schools all styles of T'ai Chi (as well as related arts such as Pa Kua Chang and Hsing-i Ch'üan) are therefore considered to be "Wu Tang style" martial arts. The schools that designate themselves "Wu Tang style" relative to the family styles mentioned above mostly claim to teach an "original style" they say was formulated by a Taoist monk called Zhang Sanfeng and taught by him in the Taoist monasteries at Wu Tang Shan. Some consider that what is practised under that name today may be a modern back-formation based on stories and popular veneration of Zhang Sanfeng (see below) as well as the martial fame of the Wu Tang monastery (there are many other martial art styles historically associated with Wu Tang besides T'ai Chi).

+ +

When tracing T'ai Chi Ch'üan's formative influences to Taoist and Buddhist monasteries, one has little more to go on than legendary tales from a modern historical perspective, but T'ai Chi Ch'üan's practical connection to and dependence upon the theories of Sung dynasty Neo-Confucianism (a conscious synthesis of Taoist, Buddhist and Confucian traditions, esp. the teachings of Mencius) is readily apparent to its practitioners. The philosophical and political landscape of that time in Chinese history is fairly well documented, even if the origin of the art later to become known as T'ai Chi Ch'üan in it is not. T'ai Chi Ch'üan's theories and practice are therefore believed by some schools to have been formulated by the Taoist monk Zhang Sanfeng in the 12th century, a time frame fitting well with when the principles of the Neo-Confucian school were making themselves felt in Chinese intellectual life. Therefore the didactic story is told that Zhang Sanfeng as a young man studied Tao Yin (??, Pinyin daoyin) breathing exercises from his Taoist teachers and martial arts at the Buddhist Shaolin monastery, eventually combining the martial forms and breathing exercises to formulate the soft or internal principles we associate with T'ai Chi Ch'üan and related martial arts. Its subsequent fame attributed to his teaching, Wu Tang monastery was known thereafter as an important martial center for many centuries, its many styles of internal kung fu preserved and refined at various Taoist temples.

+ + +

+

Family tree

+

This family tree is not comprehensive.

+
+LEGENDARY FIGURES
+   |
+Zhang Sanfeng*
+circa 12th century
+NEI CHIA
+
+   |
+Wang Zongyue*
+T'AI CHI CH'ÜAN
+   |
+THE 5 MAJOR CLASSICAL FAMILY STYLES
+   |
+Chen Wangting
+1600-1680 9th generation Chen
+CHEN STYLE
+   |
+   +-------------------------------------------------------------------+
+   |                                                                   |
+Chen Changxing                                                     Chen Youben
+1771-1853 14th generation Chen                                     circa 1800s 14th generation Chen
+Chen Old Frame                                                     Chen New Frame
+   |                                                                   |
+
+Yang Lu-ch'an                                                      Chen Qingping
+1799-1872                                                          1795-1868
+YANG STYLE                                                         Chen Small Frame, Zhao Bao Frame
+   |                                                                   |
+   +---------------------------------+-----------------------------+   |
+   |                                 |                             |   |
+Yang Pan-hou                      Yang Chien-hou                   Wu Yu-hsiang
+1837-1892                         1839-1917                        1812-1880
+Yang Small Frame                     |                             WU/HAO STYLE
+
+   |                                 +-----------------+                      |
+   |                                 |                 |                      |
+Wu Ch'uan-yü                      Yang Shao-hou     Yang Ch'eng-fu          Li I-yü
+1834-1902                         1862-1930         1883-1936               1832-1892
+   |                              Yang Small Frame  Yang Big Frame            |
+Wu Chien-ch'üan                                        |                    Hao Wei-chen
+
+1870-1942                                           Yang Shou-chung         1849-1920
+WU STYLE                                            1910-1985                 |
+108 Form                                                                      |
+   |                                                                        Sun Lu-t'ang
+Wu Kung-i                                                                   1861-1932
+1900-1970                                                                   SUN STYLE
+
+   |                                                                          |
+Wu Ta-kuei                                                                  Sun Hsing-i
+1923-1970                                                                   1891-1929                  
+                           
+MODERN FORMS        
+                                      
+from Yang Ch`eng-fu                     
+        |               
+        |              
+        |                         
+        +--------------+      
+        |              |       
+  Cheng Man-ch'ing     |   
+  1901-1975            |    
+  Short (37) Form      |     
+                       |
+              Chinese Sports Commission
+              1956
+              Beijing 24 Form
+              .
+              .
+              1989
+              42 Competition Form
+
+              (Wushu competition form combined from Sun, Wu, Chen, and Yang styles)
+
+ +

+

Notes to Family tree table

+

Names denoted by an asterisk are legendary or semilegendary figures in the lineage, which means their involvement in the lineage, while accepted by most of the major schools, isn't independently verifiable from known historical records.

+

The Cheng Man-ch'ing and Chinese Sports Commission short forms are said to be derived from Yang family forms, but neither are recognized as Yang family T'ai Chi Ch'üan by current Yang family teachers. The Chen, Yang and Wu families are now promoting their own shortened demonstration forms for competitive purposes.

+ + +

+

Modern T'ai Chi

+
+
Yang style in Shanghai +
+
Enlarge
+Yang style in Shanghai
+
+
+

T'ai Chi has become very popular in the last twenty years or so, as the baby boomers age and T'ai Chi's reputation for ameliorating the effects of aging becomes more well-known. Hospitals, clinics, community and senior centers are all hosting T'ai Chi classes in communities around the world. As a result of this popularity, there has been some divergence between those who say they practice T'ai Chi primarily for fighting, those who practice it for its aesthetic appeal (as in the shortened, modern, theatrical "Taijiquan" forms of wushu, see below), and those who are more interested in its benefits to physical and mental health. The wushu aspect is primarily for show; the forms taught for those purposes are designed to earn points in competition and are mostly unconcerned with either health maintenance or martial ability. More traditional stylists still see the two aspects of health and martial arts as equally necessary pieces of the puzzle, the yin and yang of T'ai Chi Ch'üan. The T'ai Chi "family" schools therefore still present their teachings in a martial art context even though the majority of their students nowadays profess that they are primarily interested in training for the claimed health benefits.

+ +

Along with Yoga, it is one of the fastest growing fitness and health maintenance activities, in terms of numbers of students enrolling in classes. Since there is no universal certification process and most Westerners haven't seen very much T'ai Chi and don't know what to look for, practically anyone can learn or even make up a few moves and call themselves a teacher. This is especially prevalent in the New Age community. Relatively few of these teachers even know that there are martial applications to the T'ai Chi forms. Those who do know that it is a martial art usually don't teach martially themselves. If they do teach self-defense, it is often a mixture of motions which the teachers think look like T'ai Chi Ch'üan with some other system. This is especially evident in schools located outside of China. While this phenomenon may have made some external aspects of T'ai Chi available for a wider audience, the traditional T'ai Chi family schools see the martial focus as a fundamental part of their training, both for health and self-defense purposes. They claim that while the students may not need to practice martial applications themselves to derive a benefit from T'ai Chi training, they assert that T'ai Chi teachers at least should know the martial applications to ensure that the movements they teach are done correctly and safely by their students. Also, working on the ability to protect oneself from physical attack (one of the most stressful things that can happen to a person) certainly falls under the category of complete "health maintenance." For these reasons they claim that a school not teaching those aspects somewhere in their syllabus cannot be said to be actually teaching the art itself, and will be much less likely to be able to reproduce the full health benefits that made T'ai Chi's reputation in the first place.

+ +

+

Modern forms

+ +
+
Women practicing non-martial T'ai Chi in Chinatown (New York City, New York, USA). +
+
Enlarge
+Women practicing non-martial T'ai Chi in Chinatown (New York City, New York, USA).
+
+
+ +

In order to standardize T'ai Chi Ch'üan for wushu tournament judging, and because many of the family T'ai Chi Ch'üan teachers had either moved out of China or had been forced to stop teaching after the Communist regime was established in 1949, the government sponsored Chinese Sports Committee brought together four of their wushu teachers to truncate the Yang family hand form to 24 postures in 1956. They wanted to somehow retain the look of T'ai Chi Ch'üan but make an easy to remember routine that was less difficult to teach and much less difficult to learn than longer (generally 88 to 108 posture) classical solo hand forms. In 1976, they developed a slightly longer form also for the purposes of demonstration that still didn't involve the complete memory, balance and coordination requirements of the traditional forms. This was a combination form, the Combined 48 Forms that were created by three wushu coaches headed by Professor Men Hui Feng. The combined forms were created based on simplifying and combining some features of the classical forms from four of the original styles; Ch'en, Yang, Wu, and Sun. Even though shorter modern forms don't have the conditioning benefits of the classical forms, the idea was to take what they felt were distinctive cosmetic features of these styles and to express them in a shorter time for purposes of competition.

+ +

As T'ai Chi again became popular on the Mainland, competitive forms were developed to be completed within a 6 minute time limit. In the late 1980s, the Chinese Sports Committee standardized many different competition forms. It had developed sets said to represent the four major styles as well as combined forms. These five sets of forms were created by different teams, and later approved by a committee of wushu coaches in China. All sets of forms thus created were named after their style, e.g., the Ch'en Style National Competition Form is the 56 Forms, and so on. The combined forms are The 42 Form or simply the Competition Form, as it is known in China. In the 11th Asian Games of 1990, wushu was included as an item for competition for the first time with the 42 Form being chosen to represent T'ai Chi. The International Wushu Federation (IWUF) has applied for wushu to be part of the Olympic games. If accepted, it is likely that T'ai Chi and wushu will be represented as demonstration events in 2008.

+ +

Representatives of the original T'ai Chi families do not teach the forms developed by the Chinese Sports Committee. T'ai Chi Ch'üan has historically been seen by them as a martial art, not a sport, with competitions mostly entered as a hobby or to promote one's school publicly, but with little bearing on measuring actual accomplishment in the art. Their criticisms of modern forms include that the modern, "government" routines have no standardized, internally consistent training requirements. Also, that people studying competition forms rarely train pushing hands or other power generation trainings vital to learning the martial applications of T'ai Chi Ch'üan and thereby lack the quality control traditional teachers maintain is essential for achieving the full benefits from both the health and the martial aspect of traditional T'ai Chi training.

+ +

+

Health benefits

+

Researchers have found that long-term T'ai Chi practice had favorable effects on the promotion of balance control, flexibility and cardiovascular fitness and reduced the risk of falls in elders. The studies also reported reduced pain, stress and anxiety in healthy subjects. Other studies have indicated improved cardiovascular and respiratory function in healthy subjects as well as those who had undergone coronary artery bypass surgery. Patients also benefited from T'ai Chi who suffered from heart failure, high blood pressure, heart attacks, arthritis and multiple sclerosis.

+

T'ai Chi has also been shown to reduce the symptoms of young Attention Deficit and Hyperactivity Disorder (ADHD) sufferers. T'ai Chi's gentle, low impact, movements surprisingly burn more calories than surfing and nearly as many as downhill skiing. T'ai Chi also boosts aspects of the immune system's function very significantly, and has been shown to reduce the incidence of anxiety, depression, and overall mood disturbance. (See research citations listed below.)

+ +

A pilot study has found evidence that T'ai Chi and related qigong helps reduce the severity of diabetes.[1]

+ +

+

Citations to medical research

+
    +
  • Wolf SL, Sattin RW, Kutner M. Intense T'ai Chi exercise training and fall occurrences in older, transitionally frail adults: a randomized, controlled trial. J Am Geriatr Soc. 2003 Dec; 51(12): 1693-701. PMID 14687346
  • +
  • Wang C, Collet JP, Lau J. The effect of Tai Chi on health outcomes in patients with chronic conditions: a systematic review. Arch Intern Med. 2004 Mar 8;164(5):493-501. PMID 15006825
  • + +
  • Search a listing of articles relating to the FICSIT trials and T'ai Chi [2]
  • +
  • Hernandez-Reif, M., Field, T.M., & Thimas, E. (2001). Attention deficit hyperactivity disorder: benefits from Tai Chi. Journal of Bodywork & Movement Therapies, 5(2):120-3, 2001 Apr, 5(23 ref), 120-123
  • +
  • Calorie Burning Chart [3]
  • +
  • Tai Chi boosts T-Cell counts in immune system [4]
  • +
  • Tai Chi, depression, anxiety, and mood disturbance (American Psychological Association) Journal of Psychosomatic Research, 1989 Vol 33 (2) 197-206
  • + +
  • A comprehensive listing of Tai Chi medical research links [5]
  • +
  • References to medical publications [6]
  • +
  • Tai Chi a promising remedy for diabetes, Australian Broadcasting Corporation, 20 December, 2005 - Pilot study of Qigong and tai chi in diabetes sufferers.
  • +
  • Health Research Articles on "Tai Chi as Health Therapy" for many issues, i.e. ADHD, Cardiac Health & Rehabilitation, Diabetes, High Blood Pressure, Menopause, Bone Loss, Weight Loss, etc.[7]
  • +
+ + +

+

See also

+ + +

+

External links

+ + + + + + + +
+
+
+
+
+
+
Views
+ +
+
+
Personal tools
+ +
+ + + + + + +
+
In other languages
+ +
+ +
+
+ + +
+ + + diff --git a/docs/security.txt b/docs/security.txt index 95fd0ccb..05810ade 100644 --- a/docs/security.txt +++ b/docs/security.txt @@ -1,36 +1,36 @@ - -Security - -Like anything that claims to afford security, HTML_Purifier can be circumvented -through negligence of people. This class will do its job: no more, no less, -and it's up to you to provide it the proper information and proper context -to be effective. Things to remember: - -1. UTF-8. Currently, the parser runs under the assumption that it is dealing -with UTF-8. Not ISO-8859-1 or Windows-1252, UTF-8. And definitely not "no -character encoding explicitly stated" or UTF-7. If you're not using UTF-8 as -your character encoding, you should switch. Now. (in future versions, however, -I may make the character encoding configurable, but there's only so much I -can do). Make sure any input is properly converted to UTF-8, or the parser -will mangle it badly (though it won't be a security risk if you're outputting -it as UTF-8). - -2. XHTML 1.0 Transitional. This is what the parser is outputting. For the most -part, it's compatible with HTML 4.01, but XHTML enforces some very nice things -that all web developers should use. Regardless, NO DOCTYPE is a NO. Quirks mode -has waaaay too many quirks for a little parser to handle. We did not select -strict in order to prevent ourselves from being too draconic on users. - -3. [PROJECTED] IDs. They need to be unique, but without some knowledge of the -rest of the document, it's difficult to know what's unique. I project default -behavior being a customizable prefix to all ID declarations in the document, -so make sure you don't use that prefix. Might cause problems for multiple -instances of HTML escaped output too (especially when it comes to caching). -Best to just zap them completely, perhaps. This will be configurable, and you'll -have to pick the correct one. - -4. [PROJECTED] Links. We're not going to try for spam protection (although -some hooks for such a module might be nice) but we may offer the ability to -only accept relative URLs. Pick the one that's right for you. - + +Security + +Like anything that claims to afford security, HTML_Purifier can be circumvented +through negligence of people. This class will do its job: no more, no less, +and it's up to you to provide it the proper information and proper context +to be effective. Things to remember: + +1. UTF-8. Currently, the parser runs under the assumption that it is dealing +with UTF-8. Not ISO-8859-1 or Windows-1252, UTF-8. And definitely not "no +character encoding explicitly stated" or UTF-7. If you're not using UTF-8 as +your character encoding, you should switch. Now. (in future versions, however, +I may make the character encoding configurable, but there's only so much I +can do). Make sure any input is properly converted to UTF-8, or the parser +will mangle it badly (though it won't be a security risk if you're outputting +it as UTF-8). + +2. XHTML 1.0 Transitional. This is what the parser is outputting. For the most +part, it's compatible with HTML 4.01, but XHTML enforces some very nice things +that all web developers should use. Regardless, NO DOCTYPE is a NO. Quirks mode +has waaaay too many quirks for a little parser to handle. We did not select +strict in order to prevent ourselves from being too draconic on users. + +3. [PROJECTED] IDs. They need to be unique, but without some knowledge of the +rest of the document, it's difficult to know what's unique. I project default +behavior being a customizable prefix to all ID declarations in the document, +so make sure you don't use that prefix. Might cause problems for multiple +instances of HTML escaped output too (especially when it comes to caching). +Best to just zap them completely, perhaps. This will be configurable, and you'll +have to pick the correct one. + +4. [PROJECTED] Links. We're not going to try for spam protection (although +some hooks for such a module might be nice) but we may offer the ability to +only accept relative URLs. Pick the one that's right for you. + 5. [PROJECTED] CSS. What a knotty issue. Probably will have to be configurable. \ No newline at end of file diff --git a/docs/spec.txt b/docs/spec.txt index d2c00419..5447aeb4 100644 --- a/docs/spec.txt +++ b/docs/spec.txt @@ -1,356 +1,356 @@ - -HTML Purifier Specification - by Edward Z. Yang - -== Introduction == - -There are a number of ad hoc HTML filtering solutions out there on the web -(some examples including HTML_Safe, kses and SafeHtmlChecker.class.php) that -claim to filter HTML properly, preventing malicious JavaScript and layout -breaking HTML from getting through the parser. None of them, however, -demonstrates a thorough knowledge of neither the DTD that defines the HTML -nor the caveats of HTML that cannot be expressed by a DTD. Configurable -filters (such as kses or PHP's built-in striptags() function) have trouble -validating the contents of attributes and can be subject to security attacks -due to poor configuration. Other filters take the naive approach of -blacklisting known threats and tags, failing to account for the introduction -of new technologies, new tags, new attributes or quirky browser behavior. - -However, HTML Purifier takes a different approach, one that doesn't use -specification-ignorant regexes or narrow blacklists. HTML Purifier will -decompose the whole document into tokens, and rigorously process the tokens by: -removing non-whitelisted elements, transforming bad practice tags like -into , properly checking the nesting of tags and their children and -validating all attributes according to their RFCs. - -To my knowledge, there is nothing like this on the web yet. Not even MediaWiki, -which allows an amazingly diverse mix of HTML and wikitext in its documents, -gets all the nesting quirks right. Existing solutions hope that no JavaScript -will slip through, but either do not attempt to ensure that the resulting -output is valid XHTML or send the HTML through a draconic XML parser (and yet -still get the nesting wrong: SafeHtmlChecker.class.php does not prevent -tags from being nested within each other). - -This document seeks to detail the inner workings of HTML Purifier. The first -draft was drawn up after two rough code sketches and the implementation of a -forgiving lexer. You may also be interested in the unit tests located in the -tests/ folder, which provide a living document on how exactly the filter deals -with malformed input. - -In summary: - -1. Parse document into an array of tag and text tokens (Lexer) -2. Remove all elements not on whitelist and transform certain other elements - into acceptable forms (i.e. ) -3. Make document well formed while helpfully taking into account certain quirks, - such as the fact that

tags traditionally are closed by other block-level - elements. -4. Run through all nodes and check children for proper order (especially - important for tables). -5. Validate attributes according to more restrictive definitions based on the - RFCs. -6. Translate back into a string. (Generator) - -HTML Purifier is best suited for documents that require a rich array of -HTML tags. Things like blog comments are, in all likelihood, most appropriately -written in an extremely restrictive set of markup that doesn't require -all this functionality (or not written in HTML at all). - - - -== STAGE 1 - parsing == - - Status: A (see source, mainly internals and UTF-8) - -The Lexer (currently we have three choices) handles parsing into Tokens. - -Here are the mappings for Lexer_PEARSax3 - -* Start(name, attributes) is openHandler -* End(name) is closeHandler -* Empty(name, attributes) is openHandler (is in array of empties) -* Data(parse(text)) is dataHandler -* Comment(text) is escapeHandler (has leading -) -* Data(text) is escapeHandler (has leading [, CDATA) - -Ignorable/not being implemented (although we probably want to output them raw): -* ProcessingInstructions(text) is piHandler -* JavaOrASPInstructions(text) is jaspHandler - - - -== STAGE 2 - remove foreign elements == - - Status: A- (transformations need to be implemented) - -At this point, the parser needs to start knowing about the DTD. Since we -hold everything in an associative $info array, if it's set, it's valid, and -we can include. Otherwise zap it, or attempt to figure out what they meant. -? A misspelling of ! This feature may be too sugary though. - -While we're at it, we can change the Processing Instructions and Java/ASP -Instructions into data blocks, scratch comment blocks, change CharacterData -into Data (although I don't see why we can't do that at the start). - -One last thing: the remove foreign elements has to do the element -transformations, from FONT to SPAN, etc. - - - -== STAGE 3 - make well formed == - - Status: A- (not as good as possible) - -Now we step through the whole thing and correct nesting issues. Most of the -time, it's making sure the tags match up, but there's some trickery going on -for HTML's quirks. They are: - -* Set of tags that close P - 'address', 'blockquote', 'dd', 'dir', 'div', - 'dl', 'dt', 'h1', 'h2', 'h3', 'h4', - 'h5', 'h6', 'hr', - 'ol', 'p', 'pre', - 'table', 'ul' -* Li closes li -* more? - -We also want to do translations, like from FONT to SPAN with STYLE. - - - -== STAGE 4 - check nesting == - - Status: B (table custom definition needs to be implemented) - -We know that the document is now well formed. The tokenizer should now take -things in nodes: when you hit a start tag, keep on going until you get its -ending tag, and then handle everything inside there. Fortunantely, no -fancy recursion is necessary as going to the next node is as simple as -scrolling to the next start tag. - -Suppose we have a node and encounter a problem with one of its children. -Depending on the complexity of the rule, we will either delete the children, -or delete the entire node itself. - -The simplest type of rule is zero or more valid elements, denoted like: - - ( el1 | el2 | el3 )* - -The next simplest is with one or more valid elements: - - ( li )+ - -And then you have complex cases: - - table (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+)) - map ((%block; | form | %misc;)+ | area+) - html (head, body) - head (%head.misc;, - ((title, %head.misc;, (base, %head.misc;)?) | - (base, %head.misc;, (title, %head.misc;)))) - -Each of these has to be dealt with. Case 1 is a joy, because you can zap -as many as you want, but you'll never actually have to kill the node. Two -and three need the entire node to be killed if you have a problem. This -can be problematic, as the missing node might cause its parent node to now -be incorrect. Granted, it's unlikely, and I'm fairly certain that HTML, let -alone the simplified set I'm allowing will have this problem, but it's worth -checking for. - -The way, I suppose, one would check for it, is whenever a node is removed, -scroll to it's parent start, and re-evaluate it. Make sure you're able to do -that with minimal code repetition. - -EDITOR'S NOTE: this behavior is not implemented by default, because the -default configuration has a setup that ensures that cascading node removals -will never happen. However, there will be warning signs in case someone tries -to hack it further. - -The most complex case can probably be done by using some fancy regexp -expressions and transformations. However, it doesn't seem right that, say, -a stray in a can cause the entire table to be removed. Fixing it, -however, may be too difficult (or not, see below). - -This code was excerpted from the PEAR class XML_DTD. It implements regexp -checking. - --- - -// # This actually does the validation - -// Validate the order of the children -if (!$was_error && count($dtd_children)) { - $children_list = implode(',', $children); - $regex = $this->dtd->getPcreRegex($name); - if (!preg_match('/^'.$regex.'$/', $children_list)) { - $dtd_regex = $this->dtd->getDTDRegex($name); - $this->_errors("In element <$name> the children list found:\n'$children_list', ". - "does not conform the DTD definition: '$dtd_regex'", $lineno); - } -} - --- - -// # This figures out the PcreRegex - -//$ch is a string of the allowed childs -$children = preg_split('/([^#a-zA-Z0-9_.-]+)/', $ch, -1, PREG_SPLIT_NO_EMPTY); -// check for parsed character data special case -if (in_array('#PCDATA', $children)) { - $content = '#PCDATA'; - if (count($children) == 1) { - $children = array(); - break; - } -} -// $children is not used after this - -$this->dtd['elements'][$elem_name]['child_validation_dtd_regex'] = $ch; -// Convert the DTD regex language into PCRE regex format -$reg = str_replace(',', ',?', $ch); -$reg = preg_replace('/([#a-zA-Z0-9_.-]+)/', '(,?\\0)', $reg); -$this->dtd['elements'][$elem_name]['child_validation_pcre_regex'] = $reg; - --- - -We can probably loot and steal all of this. This brilliance of this code is -amazing. I'm lovin' it! - -So, the way we define these cases should work like this: - -class ChildDef with validateChildren($children_tags) - -The function needs to parse into nodes, then into the regex array. -It can result in one of three actions: the removal of the entire parent node, -replacement of all of the original child tags with a new set of child -tags which it returns, or no changes. They shall be denoted as, respectively, - -Remove entire parent node = false -Replace child tags with this = array of tags -No changes = true - -If we remove the entire parent node, we must scroll back to the parent of the -parent. - --- - -Another few problems: EXCLUSIONS! - -a - must not contain other a elements. -pre - must not contain the img, object, big, small, sub, or sup elements. -button - must not contain the input, select, textarea, label, button, form, fieldset, - iframe or isindex elements. -label - must not contain other label elements. -form - must not contain other form elements. - -Normative exclusions straight from the horses mouth. These are SGML style, -not XML style, so we need to modify the ruleset slightly. However, the DTD -may have done this for us already. - --- - -Also, what do we do with elements if they're not allowed somewhere? We need -some sort of default behavior. I reckon that we should be allowed to: - -1. Delete the node -2. Translate it into text (not okay for areas that don't allow #PCDATA) -3. Move the node to somewhere where it is okay - -What complicates the matter is that Firefox has the ability to construct -DOMs and render invalid nestings of elements (like
asdf
). -This means that behavior for stray pcdata in ul/ol is undefined. Behavior -with data in a table gets bubbled to the start of the table (assuming -that we actually custom-make the table child validation class). - -So... I say delete the node when PCDATA isn't allowed (or the regex is too -complicated to determine where PCDATA could be inserted), and translate the node -to text when PCDATA is allowed. - --- - -Note that generic child definitions are not usually desirable: we should -implement custom handlers for each one that specify the stuff correctly. - -== STAGE 4 - check attributes == - - STATUS: N (not started) - -While we're doing all this nesting hocus-pocus, attributes are also being -checked. The reason why we need this to be done with the nesting stuff -is if a REQUIRED attribute is not there, we might need to kill the tag (or -replace it with data). Fortunantely, this is rare enough that we only have -to worry about it for certain things: - -* ! bdo - dir > replace with span, preserve attributes -* basefont - size -* param - name -* applet - width, height -* ! img - src, alt > if only alt is missing, insert filename, else remove img -* map - id -* area - alt -* form - action -* optgroup - label -* textarea - rows, cols - -As you can see, only two of them we would remotely consider for our simplified -tag set. But each has a different set of challenges. For the img tag, we'd -have to be careful about deleting it. If we do hit a snag, we can supply -a default "blank" image. - -So after that's all said and done, each of the different types of content -inside the attributes needs to be handled differently. - -ContentType(s) [RFC2045] -Charset(s) [RFC2045] -LanguageCode [RFC3066] (NMTOKEN) -Character [XML][2.2] (a single character) -Number /^\d+$/ -LinkTypes [HTML][6.12] -MediaDesc [HTML][6.13] -URI/UriList [RFC2396] -Datetime (ISO date format) -Script ... -StyleSheet [CSS] (complex) -Text CDATA -FrameTarget NMTOKEN -Length (pixel, percentage) (?:px suffix allowed?) -MultiLength (pixel, percentage, or relative) -Pixels (integer) -// map attributes omitted -ImgAlign (top|middle|bottom|left|right) -Color #NNNNNN, #NNN or color name (translate it - Black = #000000 Green = #008000 - Silver = #C0C0C0 Lime = #00FF00 - Gray = #808080 Olive = #808000 - White = #FFFFFF Yellow = #FFFF00 - Maroon = #800000 Navy = #000080 - Red = #FF0000 Blue = #0000FF - Purple = #800080 Teal = #008080 - Fuchsia= #FF00FF Aqua = #00FFFF -// plus some directly defined in the spec - -Everything else is either ID, or defined as a certain set of values. - -Unless we use reflection (which then we have to make sure the attribute exists), -we probably want to have a function like... - - validate($type, $value) where $type is like ContentType or Number - -and then pass it to a switch. - -The final problem is CSS. Get intimate with the syntax here: -http://www.w3.org/TR/CSS21/syndata.html and also note the "bad" CSS elements -that HTML_Safe defines to help determine a whitelist. - -== PART 5 - stringify == - - Status: A+ (done completely!) - -Should be fairly simple as long as we delegate to appropriate functions. -It's probably too much trouble to indent the stuff properly, so just output -stuff raw. + +HTML Purifier Specification + by Edward Z. Yang + +== Introduction == + +There are a number of ad hoc HTML filtering solutions out there on the web +(some examples including HTML_Safe, kses and SafeHtmlChecker.class.php) that +claim to filter HTML properly, preventing malicious JavaScript and layout +breaking HTML from getting through the parser. None of them, however, +demonstrates a thorough knowledge of neither the DTD that defines the HTML +nor the caveats of HTML that cannot be expressed by a DTD. Configurable +filters (such as kses or PHP's built-in striptags() function) have trouble +validating the contents of attributes and can be subject to security attacks +due to poor configuration. Other filters take the naive approach of +blacklisting known threats and tags, failing to account for the introduction +of new technologies, new tags, new attributes or quirky browser behavior. + +However, HTML Purifier takes a different approach, one that doesn't use +specification-ignorant regexes or narrow blacklists. HTML Purifier will +decompose the whole document into tokens, and rigorously process the tokens by: +removing non-whitelisted elements, transforming bad practice tags like +into , properly checking the nesting of tags and their children and +validating all attributes according to their RFCs. + +To my knowledge, there is nothing like this on the web yet. Not even MediaWiki, +which allows an amazingly diverse mix of HTML and wikitext in its documents, +gets all the nesting quirks right. Existing solutions hope that no JavaScript +will slip through, but either do not attempt to ensure that the resulting +output is valid XHTML or send the HTML through a draconic XML parser (and yet +still get the nesting wrong: SafeHtmlChecker.class.php does not prevent +tags from being nested within each other). + +This document seeks to detail the inner workings of HTML Purifier. The first +draft was drawn up after two rough code sketches and the implementation of a +forgiving lexer. You may also be interested in the unit tests located in the +tests/ folder, which provide a living document on how exactly the filter deals +with malformed input. + +In summary: + +1. Parse document into an array of tag and text tokens (Lexer) +2. Remove all elements not on whitelist and transform certain other elements + into acceptable forms (i.e. ) +3. Make document well formed while helpfully taking into account certain quirks, + such as the fact that

tags traditionally are closed by other block-level + elements. +4. Run through all nodes and check children for proper order (especially + important for tables). +5. Validate attributes according to more restrictive definitions based on the + RFCs. +6. Translate back into a string. (Generator) + +HTML Purifier is best suited for documents that require a rich array of +HTML tags. Things like blog comments are, in all likelihood, most appropriately +written in an extremely restrictive set of markup that doesn't require +all this functionality (or not written in HTML at all). + + + +== STAGE 1 - parsing == + + Status: A (see source, mainly internals and UTF-8) + +The Lexer (currently we have three choices) handles parsing into Tokens. + +Here are the mappings for Lexer_PEARSax3 + +* Start(name, attributes) is openHandler +* End(name) is closeHandler +* Empty(name, attributes) is openHandler (is in array of empties) +* Data(parse(text)) is dataHandler +* Comment(text) is escapeHandler (has leading -) +* Data(text) is escapeHandler (has leading [, CDATA) + +Ignorable/not being implemented (although we probably want to output them raw): +* ProcessingInstructions(text) is piHandler +* JavaOrASPInstructions(text) is jaspHandler + + + +== STAGE 2 - remove foreign elements == + + Status: A- (transformations need to be implemented) + +At this point, the parser needs to start knowing about the DTD. Since we +hold everything in an associative $info array, if it's set, it's valid, and +we can include. Otherwise zap it, or attempt to figure out what they meant. +? A misspelling of ! This feature may be too sugary though. + +While we're at it, we can change the Processing Instructions and Java/ASP +Instructions into data blocks, scratch comment blocks, change CharacterData +into Data (although I don't see why we can't do that at the start). + +One last thing: the remove foreign elements has to do the element +transformations, from FONT to SPAN, etc. + + + +== STAGE 3 - make well formed == + + Status: A- (not as good as possible) + +Now we step through the whole thing and correct nesting issues. Most of the +time, it's making sure the tags match up, but there's some trickery going on +for HTML's quirks. They are: + +* Set of tags that close P + 'address', 'blockquote', 'dd', 'dir', 'div', + 'dl', 'dt', 'h1', 'h2', 'h3', 'h4', + 'h5', 'h6', 'hr', + 'ol', 'p', 'pre', + 'table', 'ul' +* Li closes li +* more? + +We also want to do translations, like from FONT to SPAN with STYLE. + + + +== STAGE 4 - check nesting == + + Status: B (table custom definition needs to be implemented) + +We know that the document is now well formed. The tokenizer should now take +things in nodes: when you hit a start tag, keep on going until you get its +ending tag, and then handle everything inside there. Fortunantely, no +fancy recursion is necessary as going to the next node is as simple as +scrolling to the next start tag. + +Suppose we have a node and encounter a problem with one of its children. +Depending on the complexity of the rule, we will either delete the children, +or delete the entire node itself. + +The simplest type of rule is zero or more valid elements, denoted like: + + ( el1 | el2 | el3 )* + +The next simplest is with one or more valid elements: + + ( li )+ + +And then you have complex cases: + + table (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+)) + map ((%block; | form | %misc;)+ | area+) + html (head, body) + head (%head.misc;, + ((title, %head.misc;, (base, %head.misc;)?) | + (base, %head.misc;, (title, %head.misc;)))) + +Each of these has to be dealt with. Case 1 is a joy, because you can zap +as many as you want, but you'll never actually have to kill the node. Two +and three need the entire node to be killed if you have a problem. This +can be problematic, as the missing node might cause its parent node to now +be incorrect. Granted, it's unlikely, and I'm fairly certain that HTML, let +alone the simplified set I'm allowing will have this problem, but it's worth +checking for. + +The way, I suppose, one would check for it, is whenever a node is removed, +scroll to it's parent start, and re-evaluate it. Make sure you're able to do +that with minimal code repetition. + +EDITOR'S NOTE: this behavior is not implemented by default, because the +default configuration has a setup that ensures that cascading node removals +will never happen. However, there will be warning signs in case someone tries +to hack it further. + +The most complex case can probably be done by using some fancy regexp +expressions and transformations. However, it doesn't seem right that, say, +a stray in a

can cause the entire table to be removed. Fixing it, +however, may be too difficult (or not, see below). + +This code was excerpted from the PEAR class XML_DTD. It implements regexp +checking. + +-- + +// # This actually does the validation + +// Validate the order of the children +if (!$was_error && count($dtd_children)) { + $children_list = implode(',', $children); + $regex = $this->dtd->getPcreRegex($name); + if (!preg_match('/^'.$regex.'$/', $children_list)) { + $dtd_regex = $this->dtd->getDTDRegex($name); + $this->_errors("In element <$name> the children list found:\n'$children_list', ". + "does not conform the DTD definition: '$dtd_regex'", $lineno); + } +} + +-- + +// # This figures out the PcreRegex + +//$ch is a string of the allowed childs +$children = preg_split('/([^#a-zA-Z0-9_.-]+)/', $ch, -1, PREG_SPLIT_NO_EMPTY); +// check for parsed character data special case +if (in_array('#PCDATA', $children)) { + $content = '#PCDATA'; + if (count($children) == 1) { + $children = array(); + break; + } +} +// $children is not used after this + +$this->dtd['elements'][$elem_name]['child_validation_dtd_regex'] = $ch; +// Convert the DTD regex language into PCRE regex format +$reg = str_replace(',', ',?', $ch); +$reg = preg_replace('/([#a-zA-Z0-9_.-]+)/', '(,?\\0)', $reg); +$this->dtd['elements'][$elem_name]['child_validation_pcre_regex'] = $reg; + +-- + +We can probably loot and steal all of this. This brilliance of this code is +amazing. I'm lovin' it! + +So, the way we define these cases should work like this: + +class ChildDef with validateChildren($children_tags) + +The function needs to parse into nodes, then into the regex array. +It can result in one of three actions: the removal of the entire parent node, +replacement of all of the original child tags with a new set of child +tags which it returns, or no changes. They shall be denoted as, respectively, + +Remove entire parent node = false +Replace child tags with this = array of tags +No changes = true + +If we remove the entire parent node, we must scroll back to the parent of the +parent. + +-- + +Another few problems: EXCLUSIONS! + +a + must not contain other a elements. +pre + must not contain the img, object, big, small, sub, or sup elements. +button + must not contain the input, select, textarea, label, button, form, fieldset, + iframe or isindex elements. +label + must not contain other label elements. +form + must not contain other form elements. + +Normative exclusions straight from the horses mouth. These are SGML style, +not XML style, so we need to modify the ruleset slightly. However, the DTD +may have done this for us already. + +-- + +Also, what do we do with elements if they're not allowed somewhere? We need +some sort of default behavior. I reckon that we should be allowed to: + +1. Delete the node +2. Translate it into text (not okay for areas that don't allow #PCDATA) +3. Move the node to somewhere where it is okay + +What complicates the matter is that Firefox has the ability to construct +DOMs and render invalid nestings of elements (like
asdf
). +This means that behavior for stray pcdata in ul/ol is undefined. Behavior +with data in a table gets bubbled to the start of the table (assuming +that we actually custom-make the table child validation class). + +So... I say delete the node when PCDATA isn't allowed (or the regex is too +complicated to determine where PCDATA could be inserted), and translate the node +to text when PCDATA is allowed. + +-- + +Note that generic child definitions are not usually desirable: we should +implement custom handlers for each one that specify the stuff correctly. + +== STAGE 4 - check attributes == + + STATUS: N (not started) + +While we're doing all this nesting hocus-pocus, attributes are also being +checked. The reason why we need this to be done with the nesting stuff +is if a REQUIRED attribute is not there, we might need to kill the tag (or +replace it with data). Fortunantely, this is rare enough that we only have +to worry about it for certain things: + +* ! bdo - dir > replace with span, preserve attributes +* basefont - size +* param - name +* applet - width, height +* ! img - src, alt > if only alt is missing, insert filename, else remove img +* map - id +* area - alt +* form - action +* optgroup - label +* textarea - rows, cols + +As you can see, only two of them we would remotely consider for our simplified +tag set. But each has a different set of challenges. For the img tag, we'd +have to be careful about deleting it. If we do hit a snag, we can supply +a default "blank" image. + +So after that's all said and done, each of the different types of content +inside the attributes needs to be handled differently. + +ContentType(s) [RFC2045] +Charset(s) [RFC2045] +LanguageCode [RFC3066] (NMTOKEN) +Character [XML][2.2] (a single character) +Number /^\d+$/ +LinkTypes [HTML][6.12] +MediaDesc [HTML][6.13] +URI/UriList [RFC2396] +Datetime (ISO date format) +Script ... +StyleSheet [CSS] (complex) +Text CDATA +FrameTarget NMTOKEN +Length (pixel, percentage) (?:px suffix allowed?) +MultiLength (pixel, percentage, or relative) +Pixels (integer) +// map attributes omitted +ImgAlign (top|middle|bottom|left|right) +Color #NNNNNN, #NNN or color name (translate it + Black = #000000 Green = #008000 + Silver = #C0C0C0 Lime = #00FF00 + Gray = #808080 Olive = #808000 + White = #FFFFFF Yellow = #FFFF00 + Maroon = #800000 Navy = #000080 + Red = #FF0000 Blue = #0000FF + Purple = #800080 Teal = #008080 + Fuchsia= #FF00FF Aqua = #00FFFF +// plus some directly defined in the spec + +Everything else is either ID, or defined as a certain set of values. + +Unless we use reflection (which then we have to make sure the attribute exists), +we probably want to have a function like... + + validate($type, $value) where $type is like ContentType or Number + +and then pass it to a switch. + +The final problem is CSS. Get intimate with the syntax here: +http://www.w3.org/TR/CSS21/syndata.html and also note the "bad" CSS elements +that HTML_Safe defines to help determine a whitelist. + +== PART 5 - stringify == + + Status: A+ (done completely!) + +Should be fairly simple as long as we delegate to appropriate functions. +It's probably too much trouble to indent the stuff properly, so just output +stuff raw. diff --git a/library/HTMLPurifier.php b/library/HTMLPurifier.php index e469d480..c6577ae4 100644 --- a/library/HTMLPurifier.php +++ b/library/HTMLPurifier.php @@ -1,28 +1,28 @@ -lexer = new HTMLPurifier_Lexer(); - $this->definition = new HTMLPurifier_Definition(); - $this->generator = new HTMLPurifier_Generator(); - } - - function purify($html) { - $tokens = $this->lexer->tokenizeHTML($html); - $tokens = $this->definition->purifyTokens($tokens); - return $this->generator->generateFromTokens($tokens); - } - -} - +lexer = new HTMLPurifier_Lexer(); + $this->definition = new HTMLPurifier_Definition(); + $this->generator = new HTMLPurifier_Generator(); + } + + function purify($html) { + $tokens = $this->lexer->tokenizeHTML($html); + $tokens = $this->definition->purifyTokens($tokens); + return $this->generator->generateFromTokens($tokens); + } + +} + ?> \ No newline at end of file diff --git a/library/HTMLPurifier/AttrDef.php b/library/HTMLPurifier/AttrDef.php index 19bb3805..cdd90a60 100644 --- a/library/HTMLPurifier/AttrDef.php +++ b/library/HTMLPurifier/AttrDef.php @@ -1,11 +1,11 @@ -def = $def; - } -} - +def = $def; + } +} + ?> \ No newline at end of file diff --git a/library/HTMLPurifier/ChildDef.php b/library/HTMLPurifier/ChildDef.php index a2382cc3..ca1accca 100644 --- a/library/HTMLPurifier/ChildDef.php +++ b/library/HTMLPurifier/ChildDef.php @@ -1,169 +1,169 @@ -dtd_regex = $dtd_regex; - $this->_compileRegex(); - } - function _compileRegex() { - $raw = str_replace(' ', '', $this->dtd_regex); - if ($raw{0} != '(') { - $raw = "($raw)"; - } - $reg = str_replace(',', ',?', $raw); - $reg = preg_replace('/([#a-zA-Z0-9_.-]+)/', '(,?\\0)', $reg); - $this->_pcre_regex = $reg; - } - function validateChildren($tokens_of_children) { - $list_of_children = ''; - $nesting = 0; // depth into the nest - foreach ($tokens_of_children as $token) { - if (!empty($token->is_whitespace)) continue; - - $is_child = ($nesting == 0); // direct - - if ($token->type == 'start') { - $nesting++; - } elseif ($token->type == 'end') { - $nesting--; - } - - if ($is_child) { - $list_of_children .= $token->name . ','; - } - } - $list_of_children = rtrim($list_of_children, ','); - - $okay = - preg_match( - '/^'.$this->_pcre_regex.'$/', - $list_of_children - ); - - return (bool) $okay; - } -} -class HTMLPurifier_ChildDef_Simple extends HTMLPurifier_ChildDef -{ - var $elements = array(); - function HTMLPurifier_ChildDef_Simple($elements) { - if (is_string($elements)) { - $elements = str_replace(' ', '', $elements); - $elements = explode('|', $elements); - } - $elements = array_flip($elements); - foreach ($elements as $i => $x) $elements[$i] = true; - $this->elements = $elements; - $this->gen = new HTMLPurifier_Generator(); - } - function validateChildren() { - trigger_error('Cannot call abstract function!', E_USER_ERROR); - } -} -class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef_Simple -{ - var $type = 'required'; - function validateChildren($tokens_of_children) { - // if there are no tokens, delete parent node - if (empty($tokens_of_children)) return false; - - // the new set of children - $result = array(); - - // current depth into the nest - $nesting = 0; - - // whether or not we're deleting a node - $is_deleting = false; - - // whether or not parsed character data is allowed - // this controls whether or not we silently drop a tag - // or generate escaped HTML from it - $pcdata_allowed = isset($this->elements['#PCDATA']); - - // a little sanity check to make sure it's not ALL whitespace - $all_whitespace = true; - - foreach ($tokens_of_children as $token) { - if (!empty($token->is_whitespace)) { - $result[] = $token; - continue; - } - $all_whitespace = false; // phew, we're not talking about whitespace - - $is_child = ($nesting == 0); - - if ($token->type == 'start') { - $nesting++; - } elseif ($token->type == 'end') { - $nesting--; - } - - if ($is_child) { - $is_deleting = false; - if (!isset($this->elements[$token->name])) { - $is_deleting = true; - if ($pcdata_allowed) { - $result[] = new HTMLPurifier_Token_Text( - $this->gen->generateFromToken($token) - ); - } - continue; - } - } - if (!$is_deleting) { - $result[] = $token; - } elseif ($pcdata_allowed) { - $result[] = - new HTMLPurifier_Token_Text( - $this->gen->generateFromToken( $token ) - ); - } else { - // drop silently - } - } - if (empty($result)) return false; - if ($all_whitespace) return false; - if ($tokens_of_children == $result) return true; - return $result; - } -} - -// only altered behavior is that it returns an empty array -// instead of a false (to delete the node) -class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required -{ - var $type = 'optional'; - function validateChildren($tokens_of_children) { - $result = parent::validateChildren($tokens_of_children); - if ($result === false) return array(); - return $result; - } -} - -// placeholder -class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef -{ - var $type = 'empty'; - function HTMLPurifier_ChildDef_Empty() {} - function validateChildren() { - return false; - } -} - +dtd_regex = $dtd_regex; + $this->_compileRegex(); + } + function _compileRegex() { + $raw = str_replace(' ', '', $this->dtd_regex); + if ($raw{0} != '(') { + $raw = "($raw)"; + } + $reg = str_replace(',', ',?', $raw); + $reg = preg_replace('/([#a-zA-Z0-9_.-]+)/', '(,?\\0)', $reg); + $this->_pcre_regex = $reg; + } + function validateChildren($tokens_of_children) { + $list_of_children = ''; + $nesting = 0; // depth into the nest + foreach ($tokens_of_children as $token) { + if (!empty($token->is_whitespace)) continue; + + $is_child = ($nesting == 0); // direct + + if ($token->type == 'start') { + $nesting++; + } elseif ($token->type == 'end') { + $nesting--; + } + + if ($is_child) { + $list_of_children .= $token->name . ','; + } + } + $list_of_children = rtrim($list_of_children, ','); + + $okay = + preg_match( + '/^'.$this->_pcre_regex.'$/', + $list_of_children + ); + + return (bool) $okay; + } +} +class HTMLPurifier_ChildDef_Simple extends HTMLPurifier_ChildDef +{ + var $elements = array(); + function HTMLPurifier_ChildDef_Simple($elements) { + if (is_string($elements)) { + $elements = str_replace(' ', '', $elements); + $elements = explode('|', $elements); + } + $elements = array_flip($elements); + foreach ($elements as $i => $x) $elements[$i] = true; + $this->elements = $elements; + $this->gen = new HTMLPurifier_Generator(); + } + function validateChildren() { + trigger_error('Cannot call abstract function!', E_USER_ERROR); + } +} +class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef_Simple +{ + var $type = 'required'; + function validateChildren($tokens_of_children) { + // if there are no tokens, delete parent node + if (empty($tokens_of_children)) return false; + + // the new set of children + $result = array(); + + // current depth into the nest + $nesting = 0; + + // whether or not we're deleting a node + $is_deleting = false; + + // whether or not parsed character data is allowed + // this controls whether or not we silently drop a tag + // or generate escaped HTML from it + $pcdata_allowed = isset($this->elements['#PCDATA']); + + // a little sanity check to make sure it's not ALL whitespace + $all_whitespace = true; + + foreach ($tokens_of_children as $token) { + if (!empty($token->is_whitespace)) { + $result[] = $token; + continue; + } + $all_whitespace = false; // phew, we're not talking about whitespace + + $is_child = ($nesting == 0); + + if ($token->type == 'start') { + $nesting++; + } elseif ($token->type == 'end') { + $nesting--; + } + + if ($is_child) { + $is_deleting = false; + if (!isset($this->elements[$token->name])) { + $is_deleting = true; + if ($pcdata_allowed) { + $result[] = new HTMLPurifier_Token_Text( + $this->gen->generateFromToken($token) + ); + } + continue; + } + } + if (!$is_deleting) { + $result[] = $token; + } elseif ($pcdata_allowed) { + $result[] = + new HTMLPurifier_Token_Text( + $this->gen->generateFromToken( $token ) + ); + } else { + // drop silently + } + } + if (empty($result)) return false; + if ($all_whitespace) return false; + if ($tokens_of_children == $result) return true; + return $result; + } +} + +// only altered behavior is that it returns an empty array +// instead of a false (to delete the node) +class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required +{ + var $type = 'optional'; + function validateChildren($tokens_of_children) { + $result = parent::validateChildren($tokens_of_children); + if ($result === false) return array(); + return $result; + } +} + +// placeholder +class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef +{ + var $type = 'empty'; + function HTMLPurifier_ChildDef_Empty() {} + function validateChildren() { + return false; + } +} + ?> \ No newline at end of file diff --git a/library/HTMLPurifier/Definition.php b/library/HTMLPurifier/Definition.php index e64ba925..32bc96e7 100644 --- a/library/HTMLPurifier/Definition.php +++ b/library/HTMLPurifier/Definition.php @@ -1,445 +1,445 @@ - true, - 'blockquote' => true, - 'dd' => true, - 'dir' => true, - 'div' => true, - 'dl' => true, - 'dt' => true, - 'h1' => true, - 'h2' => true, - 'h3' => true, - 'h4' => true, - 'h5' => true, - 'h6' => true, - 'hr' => true, - 'ol' => true, - 'p' => true, - 'pre' => true, - 'table' => true, - 'ul' => true - ); - - function HTMLPurifier_Definition() { - $this->generator = new HTMLPurifier_Generator(); - } - - function loadData() { - // emulates the structure of the DTD - - // entities: prefixed with e_ and _ replaces . - // we don't use an array because that complicates interpolation - // strings are used instead of arrays because if you use arrays, - // you have to do some hideous manipulation with array_merge() - - // these are condensed, remember, with bad stuff taken out - - // transforms: font, menu, dir, center - - // DON'T MONKEY AROUND THIS unless you know what you are doing - // and also know the assumptions the code makes about what this - // contains for optimization purposes (see fixNesting) - - $e_special_extra = 'img'; - $e_special_basic = 'br | span | bdo'; - $e_special = "$e_special_basic | $e_special_extra"; - $e_fontstyle_extra = 'big | small'; - $e_fontstyle_basic = 'tt | i | b | u | s | strike'; - $e_fontstyle = "$e_fontstyle_basic | $e_fontstyle_extra"; - $e_phrase_extra = 'sub | sup'; - $e_phrase_basic = 'em | strong | dfn | code | q | samp | kbd | var'. - ' | cite | abbr | acronym'; - $e_phrase = "$e_phrase_basic | $e_phrase_extra"; - $e_inline_forms = ''; // humor the dtd - $e_misc_inline = 'ins | del'; - $e_misc = "$e_misc_inline"; - $e_inline = "a | $e_special | $e_fontstyle | $e_phrase". - " | $e_inline_forms"; - // note the casing - $e_Inline = new HTMLPurifier_ChildDef_Optional("#PCDATA | $e_inline". - " | $e_misc_inline"); - $e_heading = 'h1|h2|h3|h4|h5|h6'; - $e_lists = 'ul | ol | dl'; - $e_blocktext = 'pre | hr | blockquote | address'; - $e_block = "p | $e_heading | div | $e_lists | $e_blocktext | table"; - $e_Flow = new HTMLPurifier_ChildDef_Optional("#PCDATA | $e_block". - " | $e_inline | $e_misc"); - $e_a_content = new HTMLPurifier_ChildDef_Optional("#PCDATA | $e_special". - " | $e_fontstyle | $e_phrase | $e_inline_forms | $e_misc_inline"); - $e_pre_content = new HTMLPurifier_ChildDef_Optional("#PCDATA | a". - " | $e_special_basic | $e_fontstyle_basic | $e_phrase_basic". - " | $e_inline_forms | $e_misc_inline"); - $e_form_content = new HTMLPurifier_ChildDef_Optional(''); //unused - $e_form_button_content = new HTMLPurifier_ChildDef_Optional(''); // unused - - $this->info['ins'] = - $this->info['del'] = - $this->info['blockquote'] = - $this->info['dd'] = - $this->info['li'] = - $this->info['div'] = new HTMLPurifier_ElementDef($e_Flow); - - $this->info['em'] = - $this->info['strong'] = - $this->info['dfn'] = - $this->info['code'] = - $this->info['samp'] = - $this->info['kbd'] = - $this->info['var'] = - $this->info['code'] = - $this->info['samp'] = - $this->info['kbd'] = - $this->info['var'] = - $this->info['cite'] = - $this->info['abbr'] = - $this->info['acronym'] = - $this->info['q'] = - $this->info['sub'] = - $this->info['tt'] = - $this->info['sup'] = - $this->info['i'] = - $this->info['b'] = - $this->info['big'] = - $this->info['small'] = - $this->info['u'] = - $this->info['s'] = - $this->info['strike'] = - $this->info['bdo'] = - $this->info['span'] = - $this->info['dt'] = - $this->info['p'] = - $this->info['h1'] = - $this->info['h2'] = - $this->info['h3'] = - $this->info['h4'] = - $this->info['h5'] = - $this->info['h6'] = new HTMLPurifier_ElementDef($e_Inline); - - $this->info['ol'] = - $this->info['ul'] = - new HTMLPurifier_ElementDef( - new HTMLPurifier_ChildDef_Required('li') - ); - - $this->info['dl'] = - new HTMLPurifier_ElementDef( - new HTMLPurifier_ChildDef_Required('dt|dd') - ); - $this->info['address'] = - new HTMLPurifier_ElementDef( - new HTMLPurifier_ChildDef_Optional("#PCDATA | p | $e_inline". - " | $e_misc_inline") - ); - - $this->info['img'] = - $this->info['br'] = - $this->info['hr'] = new HTMLPurifier_ElementDef(new HTMLPurifier_ChildDef_Empty()); - - $this->info['pre'] = new HTMLPurifier_ElementDef($e_pre_content); - - $this->info['a'] = new HTMLPurifier_ElementDef($e_a_content); - - } - - function purifyTokens($tokens) { - if (empty($this->info)) $this->loadData(); - $tokens = $this->removeForeignElements($tokens); - $tokens = $this->makeWellFormed($tokens); - $tokens = $this->fixNesting($tokens); - $tokens = $this->validateAttributes($tokens); - return $tokens; - } - - function removeForeignElements($tokens) { - if (empty($this->info)) $this->loadData(); - $result = array(); - foreach($tokens as $token) { - if (!empty( $token->is_tag )) { - if (!isset($this->info[$token->name])) { - // invalid tag, generate HTML and insert in - $token = new HTMLPurifier_Token_Text( - $this->generator->generateFromToken($token) - ); - } - } elseif ($token->type == 'comment') { - // strip comments - continue; - } elseif ($token->type == 'text') { - } else { - continue; - } - $result[] = $token; - } - return $result; - } - - function makeWellFormed($tokens) { - if (empty($this->info)) $this->loadData(); - $result = array(); - $current_nesting = array(); - foreach ($tokens as $token) { - if (empty( $token->is_tag )) { - $result[] = $token; - continue; - } - $info = $this->info[$token->name]; // assumption but valid - - // test if it claims to be a start tag but is empty - if ($info->child_def->type == 'empty' && - $token->type == 'start' ) { - - $result[] = new HTMLPurifier_Token_Empty($token->name, - $token->attributes); - continue; - } - - // test if it claims to be empty but really is a start tag - if ($info->child_def->type != 'empty' && - $token->type == 'empty' ) { - - $result[] = new HTMLPurifier_Token_Start($token->name, - $token->attributes); - $result[] = new HTMLPurifier_Token_End($token->name); - - continue; - } - - // automatically insert empty tags - if ($token->type == 'empty') { - $result[] = $token; - continue; - } - - // we give start tags precedence, so automatically accept unless... - // it's one of those special cases - if ($token->type == 'start') { - - // if there's a parent, check for special case - if (!empty($current_nesting)) { - $current_parent = array_pop($current_nesting); - - // check if we're closing a P tag - if ($current_parent->name == 'p' && - isset($this->info_closes_p[$token->name]) - ) { - $result[] = new HTMLPurifier_Token_End('p'); - $result[] = $token; - $current_nesting[] = $token; - continue; - } - - // check if we're closing a LI tag - if ($current_parent->name == 'li' && - $token->name == 'li' - ) { - $result[] = new HTMLPurifier_Token_End('li'); - $result[] = $token; - $current_nesting[] = $token; - continue; - } - - // this is more TIDY stuff - // we should also get some TABLE related code - // mismatched h# - - $current_nesting[] = $current_parent; // undo the pop - } - - $result[] = $token; - $current_nesting[] = $token; - continue; - } - - // sanity check - if ($token->type != 'end') continue; - - // okay, we're dealing with a closing tag - - // make sure that we have something open - if (empty($current_nesting)) { - $result[] = new HTMLPurifier_Token_Text( - $this->generator->generateFromToken($token) - ); - continue; - } - - // first, check for the simplest case: everything closes neatly - - // current_nesting is modified - $current_parent = array_pop($current_nesting); - if ($current_parent->name == $token->name) { - $result[] = $token; - continue; - } - - // undo the array_pop - $current_nesting[] = $current_parent; - - // okay, so we're trying to close the wrong tag - - // scroll back the entire nest, trying to find our tag - // feature could be to specify how far you'd like to go - $size = count($current_nesting); - // -2 because -1 is the last element, but we already checked that - $skipped_tags = false; - for ($i = $size - 2; $i >= 0; $i--) { - if ($current_nesting[$i]->name == $token->name) { - // current nesting is modified - $skipped_tags = array_splice($current_nesting, $i); - break; - } - } - - // we still didn't find the tag, so translate to text - if ($skipped_tags === false) { - $result[] = new HTMLPurifier_Token_Text( - $this->generator->generateFromToken($token) - ); - continue; - } - - // okay, we found it, close all the skipped tags - // note that skipped tags contains the element we need closed - $size = count($skipped_tags); - for ($i = $size - 1; $i >= 0; $i--) { - $result[] = new HTMLPurifier_Token_End($skipped_tags[$i]->name); - } - - // done! - - } - - // we're at the end now, fix all still unclosed tags - - if (!empty($current_nesting)) { - $size = count($current_nesting); - for ($i = $size - 1; $i >= 0; $i--) { - $result[] = - new HTMLPurifier_Token_End($current_nesting[$i]->name); - } - } - - return $result; - } - - function fixNesting($tokens) { - if (empty($this->info)) $this->loadData(); - - // insert implicit "parent" node, will be removed at end - array_unshift($tokens, new HTMLPurifier_Token_Start('div')); - $tokens[] = new HTMLPurifier_Token_End('div'); - - for ($i = 0, $size = count($tokens) ; $i < $size; ) { - - $child_tokens = array(); - - // scroll to the end of this node, and report number - for ($j = $i, $depth = 0; ; $j++) { - if ($tokens[$j]->type == 'start') { - $depth++; - // skip token assignment on first iteration - if ($depth == 1) continue; - } elseif ($tokens[$j]->type == 'end') { - $depth--; - // skip token assignment on last iteration - if ($depth == 0) break; - } - $child_tokens[] = $tokens[$j]; - } - - // $i is index of start token - // $j is index of end token - - // have DTD child def validate children - $element_def = $this->info[$tokens[$i]->name]; - $result = $element_def->child_def->validateChildren($child_tokens); - - // process result - if ($result === true) { - - // leave the nodes as is - - } elseif($result === false) { - - // WARNING WARNING WARNING!!! - // While for the original DTD, there will never be - // cascading removal, more complex ones may have such - // a problem. - - // If you modify the info array such that an element - // that requires children may contain a child that requires - // children, you need to also scroll back and re-check that - // elements parent node - - $length = $j - $i + 1; - - // remove entire node - array_splice($tokens, $i, $length); - - // change size - $size -= $length; - - // ensure that we scroll to the next node - $i--; - - } else { - - $length = $j - $i - 1; - - // replace node with $result - array_splice($tokens, $i + 1, $length, $result); - - // change size - $size -= $length; - $size += count($result); - - } - - // scroll to next node - $i++; - while ($i < $size and $tokens[$i]->type != 'start') $i++; - - } - - // remove implicit divs - array_shift($tokens); - array_pop($tokens); - - return $tokens; - - } - - function validateAttributes($tokens) { - if (empty($this->info)) $this->loadData(); - - } - -} - -class HTMLPurifier_ElementDef -{ - - var $child_def; - var $attr_def = array(); - - function HTMLPurifier_ElementDef($child_def, $attr_def = array()) { - $this->child_def = $child_def; - $this->attr_def = $attr_def; - } - -} - + true, + 'blockquote' => true, + 'dd' => true, + 'dir' => true, + 'div' => true, + 'dl' => true, + 'dt' => true, + 'h1' => true, + 'h2' => true, + 'h3' => true, + 'h4' => true, + 'h5' => true, + 'h6' => true, + 'hr' => true, + 'ol' => true, + 'p' => true, + 'pre' => true, + 'table' => true, + 'ul' => true + ); + + function HTMLPurifier_Definition() { + $this->generator = new HTMLPurifier_Generator(); + } + + function loadData() { + // emulates the structure of the DTD + + // entities: prefixed with e_ and _ replaces . + // we don't use an array because that complicates interpolation + // strings are used instead of arrays because if you use arrays, + // you have to do some hideous manipulation with array_merge() + + // these are condensed, remember, with bad stuff taken out + + // transforms: font, menu, dir, center + + // DON'T MONKEY AROUND THIS unless you know what you are doing + // and also know the assumptions the code makes about what this + // contains for optimization purposes (see fixNesting) + + $e_special_extra = 'img'; + $e_special_basic = 'br | span | bdo'; + $e_special = "$e_special_basic | $e_special_extra"; + $e_fontstyle_extra = 'big | small'; + $e_fontstyle_basic = 'tt | i | b | u | s | strike'; + $e_fontstyle = "$e_fontstyle_basic | $e_fontstyle_extra"; + $e_phrase_extra = 'sub | sup'; + $e_phrase_basic = 'em | strong | dfn | code | q | samp | kbd | var'. + ' | cite | abbr | acronym'; + $e_phrase = "$e_phrase_basic | $e_phrase_extra"; + $e_inline_forms = ''; // humor the dtd + $e_misc_inline = 'ins | del'; + $e_misc = "$e_misc_inline"; + $e_inline = "a | $e_special | $e_fontstyle | $e_phrase". + " | $e_inline_forms"; + // note the casing + $e_Inline = new HTMLPurifier_ChildDef_Optional("#PCDATA | $e_inline". + " | $e_misc_inline"); + $e_heading = 'h1|h2|h3|h4|h5|h6'; + $e_lists = 'ul | ol | dl'; + $e_blocktext = 'pre | hr | blockquote | address'; + $e_block = "p | $e_heading | div | $e_lists | $e_blocktext | table"; + $e_Flow = new HTMLPurifier_ChildDef_Optional("#PCDATA | $e_block". + " | $e_inline | $e_misc"); + $e_a_content = new HTMLPurifier_ChildDef_Optional("#PCDATA | $e_special". + " | $e_fontstyle | $e_phrase | $e_inline_forms | $e_misc_inline"); + $e_pre_content = new HTMLPurifier_ChildDef_Optional("#PCDATA | a". + " | $e_special_basic | $e_fontstyle_basic | $e_phrase_basic". + " | $e_inline_forms | $e_misc_inline"); + $e_form_content = new HTMLPurifier_ChildDef_Optional(''); //unused + $e_form_button_content = new HTMLPurifier_ChildDef_Optional(''); // unused + + $this->info['ins'] = + $this->info['del'] = + $this->info['blockquote'] = + $this->info['dd'] = + $this->info['li'] = + $this->info['div'] = new HTMLPurifier_ElementDef($e_Flow); + + $this->info['em'] = + $this->info['strong'] = + $this->info['dfn'] = + $this->info['code'] = + $this->info['samp'] = + $this->info['kbd'] = + $this->info['var'] = + $this->info['code'] = + $this->info['samp'] = + $this->info['kbd'] = + $this->info['var'] = + $this->info['cite'] = + $this->info['abbr'] = + $this->info['acronym'] = + $this->info['q'] = + $this->info['sub'] = + $this->info['tt'] = + $this->info['sup'] = + $this->info['i'] = + $this->info['b'] = + $this->info['big'] = + $this->info['small'] = + $this->info['u'] = + $this->info['s'] = + $this->info['strike'] = + $this->info['bdo'] = + $this->info['span'] = + $this->info['dt'] = + $this->info['p'] = + $this->info['h1'] = + $this->info['h2'] = + $this->info['h3'] = + $this->info['h4'] = + $this->info['h5'] = + $this->info['h6'] = new HTMLPurifier_ElementDef($e_Inline); + + $this->info['ol'] = + $this->info['ul'] = + new HTMLPurifier_ElementDef( + new HTMLPurifier_ChildDef_Required('li') + ); + + $this->info['dl'] = + new HTMLPurifier_ElementDef( + new HTMLPurifier_ChildDef_Required('dt|dd') + ); + $this->info['address'] = + new HTMLPurifier_ElementDef( + new HTMLPurifier_ChildDef_Optional("#PCDATA | p | $e_inline". + " | $e_misc_inline") + ); + + $this->info['img'] = + $this->info['br'] = + $this->info['hr'] = new HTMLPurifier_ElementDef(new HTMLPurifier_ChildDef_Empty()); + + $this->info['pre'] = new HTMLPurifier_ElementDef($e_pre_content); + + $this->info['a'] = new HTMLPurifier_ElementDef($e_a_content); + + } + + function purifyTokens($tokens) { + if (empty($this->info)) $this->loadData(); + $tokens = $this->removeForeignElements($tokens); + $tokens = $this->makeWellFormed($tokens); + $tokens = $this->fixNesting($tokens); + $tokens = $this->validateAttributes($tokens); + return $tokens; + } + + function removeForeignElements($tokens) { + if (empty($this->info)) $this->loadData(); + $result = array(); + foreach($tokens as $token) { + if (!empty( $token->is_tag )) { + if (!isset($this->info[$token->name])) { + // invalid tag, generate HTML and insert in + $token = new HTMLPurifier_Token_Text( + $this->generator->generateFromToken($token) + ); + } + } elseif ($token->type == 'comment') { + // strip comments + continue; + } elseif ($token->type == 'text') { + } else { + continue; + } + $result[] = $token; + } + return $result; + } + + function makeWellFormed($tokens) { + if (empty($this->info)) $this->loadData(); + $result = array(); + $current_nesting = array(); + foreach ($tokens as $token) { + if (empty( $token->is_tag )) { + $result[] = $token; + continue; + } + $info = $this->info[$token->name]; // assumption but valid + + // test if it claims to be a start tag but is empty + if ($info->child_def->type == 'empty' && + $token->type == 'start' ) { + + $result[] = new HTMLPurifier_Token_Empty($token->name, + $token->attributes); + continue; + } + + // test if it claims to be empty but really is a start tag + if ($info->child_def->type != 'empty' && + $token->type == 'empty' ) { + + $result[] = new HTMLPurifier_Token_Start($token->name, + $token->attributes); + $result[] = new HTMLPurifier_Token_End($token->name); + + continue; + } + + // automatically insert empty tags + if ($token->type == 'empty') { + $result[] = $token; + continue; + } + + // we give start tags precedence, so automatically accept unless... + // it's one of those special cases + if ($token->type == 'start') { + + // if there's a parent, check for special case + if (!empty($current_nesting)) { + $current_parent = array_pop($current_nesting); + + // check if we're closing a P tag + if ($current_parent->name == 'p' && + isset($this->info_closes_p[$token->name]) + ) { + $result[] = new HTMLPurifier_Token_End('p'); + $result[] = $token; + $current_nesting[] = $token; + continue; + } + + // check if we're closing a LI tag + if ($current_parent->name == 'li' && + $token->name == 'li' + ) { + $result[] = new HTMLPurifier_Token_End('li'); + $result[] = $token; + $current_nesting[] = $token; + continue; + } + + // this is more TIDY stuff + // we should also get some TABLE related code + // mismatched h# + + $current_nesting[] = $current_parent; // undo the pop + } + + $result[] = $token; + $current_nesting[] = $token; + continue; + } + + // sanity check + if ($token->type != 'end') continue; + + // okay, we're dealing with a closing tag + + // make sure that we have something open + if (empty($current_nesting)) { + $result[] = new HTMLPurifier_Token_Text( + $this->generator->generateFromToken($token) + ); + continue; + } + + // first, check for the simplest case: everything closes neatly + + // current_nesting is modified + $current_parent = array_pop($current_nesting); + if ($current_parent->name == $token->name) { + $result[] = $token; + continue; + } + + // undo the array_pop + $current_nesting[] = $current_parent; + + // okay, so we're trying to close the wrong tag + + // scroll back the entire nest, trying to find our tag + // feature could be to specify how far you'd like to go + $size = count($current_nesting); + // -2 because -1 is the last element, but we already checked that + $skipped_tags = false; + for ($i = $size - 2; $i >= 0; $i--) { + if ($current_nesting[$i]->name == $token->name) { + // current nesting is modified + $skipped_tags = array_splice($current_nesting, $i); + break; + } + } + + // we still didn't find the tag, so translate to text + if ($skipped_tags === false) { + $result[] = new HTMLPurifier_Token_Text( + $this->generator->generateFromToken($token) + ); + continue; + } + + // okay, we found it, close all the skipped tags + // note that skipped tags contains the element we need closed + $size = count($skipped_tags); + for ($i = $size - 1; $i >= 0; $i--) { + $result[] = new HTMLPurifier_Token_End($skipped_tags[$i]->name); + } + + // done! + + } + + // we're at the end now, fix all still unclosed tags + + if (!empty($current_nesting)) { + $size = count($current_nesting); + for ($i = $size - 1; $i >= 0; $i--) { + $result[] = + new HTMLPurifier_Token_End($current_nesting[$i]->name); + } + } + + return $result; + } + + function fixNesting($tokens) { + if (empty($this->info)) $this->loadData(); + + // insert implicit "parent" node, will be removed at end + array_unshift($tokens, new HTMLPurifier_Token_Start('div')); + $tokens[] = new HTMLPurifier_Token_End('div'); + + for ($i = 0, $size = count($tokens) ; $i < $size; ) { + + $child_tokens = array(); + + // scroll to the end of this node, and report number + for ($j = $i, $depth = 0; ; $j++) { + if ($tokens[$j]->type == 'start') { + $depth++; + // skip token assignment on first iteration + if ($depth == 1) continue; + } elseif ($tokens[$j]->type == 'end') { + $depth--; + // skip token assignment on last iteration + if ($depth == 0) break; + } + $child_tokens[] = $tokens[$j]; + } + + // $i is index of start token + // $j is index of end token + + // have DTD child def validate children + $element_def = $this->info[$tokens[$i]->name]; + $result = $element_def->child_def->validateChildren($child_tokens); + + // process result + if ($result === true) { + + // leave the nodes as is + + } elseif($result === false) { + + // WARNING WARNING WARNING!!! + // While for the original DTD, there will never be + // cascading removal, more complex ones may have such + // a problem. + + // If you modify the info array such that an element + // that requires children may contain a child that requires + // children, you need to also scroll back and re-check that + // elements parent node + + $length = $j - $i + 1; + + // remove entire node + array_splice($tokens, $i, $length); + + // change size + $size -= $length; + + // ensure that we scroll to the next node + $i--; + + } else { + + $length = $j - $i - 1; + + // replace node with $result + array_splice($tokens, $i + 1, $length, $result); + + // change size + $size -= $length; + $size += count($result); + + } + + // scroll to next node + $i++; + while ($i < $size and $tokens[$i]->type != 'start') $i++; + + } + + // remove implicit divs + array_shift($tokens); + array_pop($tokens); + + return $tokens; + + } + + function validateAttributes($tokens) { + if (empty($this->info)) $this->loadData(); + + } + +} + +class HTMLPurifier_ElementDef +{ + + var $child_def; + var $attr_def = array(); + + function HTMLPurifier_ElementDef($child_def, $attr_def = array()) { + $this->child_def = $child_def; + $this->attr_def = $attr_def; + } + +} + ?> \ No newline at end of file diff --git a/library/HTMLPurifier/Generator.php b/library/HTMLPurifier/Generator.php index 0bd0f602..d8eabd61 100644 --- a/library/HTMLPurifier/Generator.php +++ b/library/HTMLPurifier/Generator.php @@ -1,45 +1,45 @@ -generateFromToken($token); - } - return $html; - } - - function generateFromToken($token) { - if ($token->type == 'start') { - $attr = $this->generateAttributes($token->attributes); - return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>'; - - } elseif ($token->type == 'end') { - return 'name . '>'; - - } elseif ($token->type == 'empty') { - $attr = $this->generateAttributes($token->attributes); - return '<' . $token->name . ($attr ? ' ' : '') . $attr . ' />'; - - } elseif ($token->type == 'text') { - return htmlentities($token->data, ENT_COMPAT, 'UTF-8'); - - } else { - return ''; - - } - } - - function generateAttributes($assoc_array_of_attributes) { - $html = ''; - foreach ($assoc_array_of_attributes as $key => $value) { - $html .= $key.'="'.htmlentities($value, ENT_COMPAT, 'UTF-8').'" '; - } - return rtrim($html); - } - -} - +generateFromToken($token); + } + return $html; + } + + function generateFromToken($token) { + if ($token->type == 'start') { + $attr = $this->generateAttributes($token->attributes); + return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>'; + + } elseif ($token->type == 'end') { + return 'name . '>'; + + } elseif ($token->type == 'empty') { + $attr = $this->generateAttributes($token->attributes); + return '<' . $token->name . ($attr ? ' ' : '') . $attr . ' />'; + + } elseif ($token->type == 'text') { + return htmlentities($token->data, ENT_COMPAT, 'UTF-8'); + + } else { + return ''; + + } + } + + function generateAttributes($assoc_array_of_attributes) { + $html = ''; + foreach ($assoc_array_of_attributes as $key => $value) { + $html .= $key.'="'.htmlentities($value, ENT_COMPAT, 'UTF-8').'" '; + } + return rtrim($html); + } + +} + ?> \ No newline at end of file diff --git a/library/HTMLPurifier/Lexer/DirectLex.php b/library/HTMLPurifier/Lexer/DirectLex.php index 56d421b7..d88dd043 100644 --- a/library/HTMLPurifier/Lexer/DirectLex.php +++ b/library/HTMLPurifier/Lexer/DirectLex.php @@ -1,354 +1,354 @@ -_entity_utf8 = version_compare(PHP_VERSION, '5', '>='); - } - - // this is QUITE a knotty problem - // - // The main trouble is that, even while assuming UTF-8 is what we're - // using, we've got to deal with HTML entities (like —) - // Not even sure if the PHP 5 decoding function does that. Plus, - // SimpleTest doesn't use UTF-8! - // - // However, we MUST parse everything possible, because once you get - // to the HTML generator, it will escape everything possible (although - // that may not be correct, and we should be using htmlspecialchars() ). - // - // Nevertheless, strictly XML speaking, we cannot assume any character - // entities are defined except the htmlspecialchars() ones, so leaving - // the entities inside HERE is not acceptable. (plus, htmlspecialchars - // might convert them anyway). So EVERYTHING must get parsed. - // - // We may need to roll our own character entity lookup table. It's only - // about 250, fortunantely, the decimal/hex ones map cleanly to UTF-8. - function parseData($string) { - // we may want to let the user do a different char encoding, - // although there is NO REASON why they shouldn't be able - // to convert it to UTF-8 before they pass it to us - - // no support for less than PHP 4.3 - if ($this->_entity_utf8) { - // PHP 5+, UTF-8 is nicely supported - return @html_entity_decode($string, ENT_QUOTES, 'UTF-8'); - } else { - // PHP 4, do compat stuff - $string = html_entity_decode($string, ENT_QUOTES, 'ISO-8859-1'); - // get the numeric UTF-8 stuff - $string = preg_replace('/&#(\d+);/me', "chr(\\1)", $string); - $string = preg_replace('/&#x([a-f0-9]+);/mei',"chr(0x\\1)",$string); - // get the stringy UTF-8 stuff - return $string; - } - } - - function nextQuote($string, $offset = 0) { - $next = strcspn($string, '"\'', $offset) + $offset; - return strlen($string) == $next ? false : $next; - } - - function nextWhiteSpace($string, $offset = 0) { - $next = strcspn($string, "\x20\x09\x0D\x0A", $offset) + $offset; - return strlen($string) == $next ? false : $next; - } - - function tokenizeHTML($string) { - - // some quick checking (if empty, return empty) - $string = @ (string) $string; - if ($string == '') return array(); - - $cursor = 0; // our location in the text - $inside_tag = false; // whether or not we're parsing the inside of a tag - $array = array(); // result array - - // infinite loop protection - // has to be pretty big, since html docs can be big - // we're allow two hundred thousand tags... more than enough? - $loops = 0; - - while(true) { - - // infinite loop protection - if (++$loops > 200000) return array(); - - $position_next_lt = strpos($string, '<', $cursor); - $position_next_gt = strpos($string, '>', $cursor); - - // triggers on "asdf" but not "asdf " - if ($position_next_lt === $cursor) { - $inside_tag = true; - $cursor++; - } - - if (!$inside_tag && $position_next_lt !== false) { - // We are not inside tag and there still is another tag to parse - $array[] = new - HTMLPurifier_Token_Text( - html_entity_decode( - substr( - $string, $cursor, $position_next_lt - $cursor - ), - ENT_QUOTES - ) - ); - $cursor = $position_next_lt + 1; - $inside_tag = true; - continue; - } elseif (!$inside_tag) { - // We are not inside tag but there are no more tags - // If we're already at the end, break - if ($cursor === strlen($string)) break; - // Create Text of rest of string - $array[] = new - HTMLPurifier_Token_Text( - html_entity_decode( - substr( - $string, $cursor - ), - ENT_QUOTES - ) - ); - break; - } elseif ($inside_tag && $position_next_gt !== false) { - // We are in tag and it is well formed - // Grab the internals of the tag - $segment = substr($string, $cursor, $position_next_gt-$cursor); - - // Check if it's a comment - if ( - substr($segment,0,3) == '!--' && - substr($segment,strlen($segment)-2,2) == '--' - ) { - $array[] = new - HTMLPurifier_Token_Comment( - substr( - $segment, 3, strlen($segment) - 5 - ) - ); - $inside_tag = false; - $cursor = $position_next_gt + 1; - continue; - } - - // Check if it's an end tag - $is_end_tag = (strpos($segment,'/') === 0); - if ($is_end_tag) { - $type = substr($segment, 1); - $array[] = new HTMLPurifier_Token_End($type); - $inside_tag = false; - $cursor = $position_next_gt + 1; - continue; - } - - // Check if it is explicitly self closing, if so, remove - // trailing slash. Remember, we could have a tag like
, so - // any later token processing scripts must convert improperly - // classified EmptyTags from StartTags. - $is_self_closing= (strpos($segment,'/') === strlen($segment)-1); - if ($is_self_closing) { - $segment = substr($segment, 0, strlen($segment) - 1); - } - - // Check if there are any attributes - $position_first_space = $this->nextWhiteSpace($segment); - if ($position_first_space === false) { - if ($is_self_closing) { - $array[] = new HTMLPurifier_Token_Empty($segment); - } else { - $array[] = new HTMLPurifier_Token_Start($segment); - } - $inside_tag = false; - $cursor = $position_next_gt + 1; - continue; - } - - // Grab out all the data - $type = substr($segment, 0, $position_first_space); - $attribute_string = - trim( - substr( - $segment, $position_first_space - ) - ); - if ($attribute_string) { - $attributes = $this->tokenizeAttributeString( - $attribute_string - ); - } else { - $attributes = array(); - } - - if ($is_self_closing) { - $array[] = new HTMLPurifier_Token_Empty($type, $attributes); - } else { - $array[] = new HTMLPurifier_Token_Start($type, $attributes); - } - $cursor = $position_next_gt + 1; - $inside_tag = false; - continue; - } else { - $array[] = new - HTMLPurifier_Token_Text( - '<' . - html_entity_decode( - substr($string, $cursor), - ENT_QUOTES - ) - ); - break; - } - break; - } - return $array; - } - - function tokenizeAttributeString($string) { - $string = (string) $string; // quick typecast - - if ($string == '') return array(); // no attributes - - // let's see if we can abort as quickly as possible - // one equal sign, no spaces => one attribute - $num_equal = substr_count($string, '='); - $has_space = strpos($string, ' '); - if ($num_equal === 0 && !$has_space) { - // bool attribute - return array($string => $string); - } elseif ($num_equal === 1 && !$has_space) { - // only one attribute - list($key, $quoted_value) = explode('=', $string); - $quoted_value = trim($quoted_value); - if (!$key) return array(); - if (!$quoted_value) return array($key => ''); - $first_char = @$quoted_value[0]; - $last_char = @$quoted_value[strlen($quoted_value)-1]; - - $same_quote = ($first_char == $last_char); - $open_quote = ($first_char == '"' || $first_char == "'"); - - if ( $same_quote && $open_quote) { - // well behaved - $value = substr($quoted_value, 1, strlen($quoted_value) - 2); - } else { - // not well behaved - if ($open_quote) { - $value = substr($quoted_value, 1); - } else { - $value = $quoted_value; - } - } - return array($key => $value); - } - - // setup loop environment - $array = array(); // return assoc array of attributes - $cursor = 0; // current position in string (moves forward) - $size = strlen($string); // size of the string (stays the same) - - // if we have unquoted attributes, the parser expects a terminating - // space, so let's guarantee that there's always a terminating space. - $string .= ' '; - - // infinite loop protection - $loops = 0; - - while(true) { - - // infinite loop protection - if (++$loops > 1000) return array(); - - if ($cursor >= $size) { - break; - } - - $cursor += ($value = strspn($string, "\x20\x09\x0D\x0A", $cursor)); - - $position_next_space = $this->nextWhiteSpace($string, $cursor); - $position_next_equal = strpos($string, '=', $cursor); - - // grab the key - - $key_begin = $cursor; //we're currently at the start of the key - - // scroll past all characters that are the key (not whitespace or =) - $cursor += strcspn($string, "\x20\x09\x0D\x0A=", $cursor); - - $key_end = $cursor; // now at the end of the key - - $key = substr($string, $key_begin, $key_end - $key_begin); - - if (!$key) continue; // empty key - - // scroll past all whitespace - $cursor += strspn($string, "\x20\x09\x0D\x0A", $cursor); - - if ($cursor >= $size) { - $array[$key] = $key; - break; - } - - // if the next character is an equal sign, we've got a regular - // pair, otherwise, it's a bool attribute - $first_char = @$string[$cursor]; - - if ($first_char == '=') { - // key="value" - - $cursor++; - $cursor += strspn($string, "\x20\x09\x0D\x0A", $cursor); - - // we might be in front of a quote right now - - $char = @$string[$cursor]; - - if ($char == '"' || $char == "'") { - // it's quoted, end bound is $char - $cursor++; - $value_begin = $cursor; - $cursor = strpos($string, $char, $cursor); - $value_end = $cursor; - } else { - // it's not quoted, end bound is whitespace - $value_begin = $cursor; - $cursor += strcspn($string, "\x20\x09\x0D\x0A", $cursor); - $value_end = $cursor; - } - - $value = substr($string, $value_begin, $value_end - $value_begin); - $array[$key] = $value; - $cursor++; - - } else { - // boolattr - if ($key !== '') { - $array[$key] = $key; - } - - } - } - return $array; - } - -} - +_entity_utf8 = version_compare(PHP_VERSION, '5', '>='); + } + + // this is QUITE a knotty problem + // + // The main trouble is that, even while assuming UTF-8 is what we're + // using, we've got to deal with HTML entities (like —) + // Not even sure if the PHP 5 decoding function does that. Plus, + // SimpleTest doesn't use UTF-8! + // + // However, we MUST parse everything possible, because once you get + // to the HTML generator, it will escape everything possible (although + // that may not be correct, and we should be using htmlspecialchars() ). + // + // Nevertheless, strictly XML speaking, we cannot assume any character + // entities are defined except the htmlspecialchars() ones, so leaving + // the entities inside HERE is not acceptable. (plus, htmlspecialchars + // might convert them anyway). So EVERYTHING must get parsed. + // + // We may need to roll our own character entity lookup table. It's only + // about 250, fortunantely, the decimal/hex ones map cleanly to UTF-8. + function parseData($string) { + // we may want to let the user do a different char encoding, + // although there is NO REASON why they shouldn't be able + // to convert it to UTF-8 before they pass it to us + + // no support for less than PHP 4.3 + if ($this->_entity_utf8) { + // PHP 5+, UTF-8 is nicely supported + return @html_entity_decode($string, ENT_QUOTES, 'UTF-8'); + } else { + // PHP 4, do compat stuff + $string = html_entity_decode($string, ENT_QUOTES, 'ISO-8859-1'); + // get the numeric UTF-8 stuff + $string = preg_replace('/&#(\d+);/me', "chr(\\1)", $string); + $string = preg_replace('/&#x([a-f0-9]+);/mei',"chr(0x\\1)",$string); + // get the stringy UTF-8 stuff + return $string; + } + } + + function nextQuote($string, $offset = 0) { + $next = strcspn($string, '"\'', $offset) + $offset; + return strlen($string) == $next ? false : $next; + } + + function nextWhiteSpace($string, $offset = 0) { + $next = strcspn($string, "\x20\x09\x0D\x0A", $offset) + $offset; + return strlen($string) == $next ? false : $next; + } + + function tokenizeHTML($string) { + + // some quick checking (if empty, return empty) + $string = @ (string) $string; + if ($string == '') return array(); + + $cursor = 0; // our location in the text + $inside_tag = false; // whether or not we're parsing the inside of a tag + $array = array(); // result array + + // infinite loop protection + // has to be pretty big, since html docs can be big + // we're allow two hundred thousand tags... more than enough? + $loops = 0; + + while(true) { + + // infinite loop protection + if (++$loops > 200000) return array(); + + $position_next_lt = strpos($string, '<', $cursor); + $position_next_gt = strpos($string, '>', $cursor); + + // triggers on "asdf" but not "asdf " + if ($position_next_lt === $cursor) { + $inside_tag = true; + $cursor++; + } + + if (!$inside_tag && $position_next_lt !== false) { + // We are not inside tag and there still is another tag to parse + $array[] = new + HTMLPurifier_Token_Text( + html_entity_decode( + substr( + $string, $cursor, $position_next_lt - $cursor + ), + ENT_QUOTES + ) + ); + $cursor = $position_next_lt + 1; + $inside_tag = true; + continue; + } elseif (!$inside_tag) { + // We are not inside tag but there are no more tags + // If we're already at the end, break + if ($cursor === strlen($string)) break; + // Create Text of rest of string + $array[] = new + HTMLPurifier_Token_Text( + html_entity_decode( + substr( + $string, $cursor + ), + ENT_QUOTES + ) + ); + break; + } elseif ($inside_tag && $position_next_gt !== false) { + // We are in tag and it is well formed + // Grab the internals of the tag + $segment = substr($string, $cursor, $position_next_gt-$cursor); + + // Check if it's a comment + if ( + substr($segment,0,3) == '!--' && + substr($segment,strlen($segment)-2,2) == '--' + ) { + $array[] = new + HTMLPurifier_Token_Comment( + substr( + $segment, 3, strlen($segment) - 5 + ) + ); + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Check if it's an end tag + $is_end_tag = (strpos($segment,'/') === 0); + if ($is_end_tag) { + $type = substr($segment, 1); + $array[] = new HTMLPurifier_Token_End($type); + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Check if it is explicitly self closing, if so, remove + // trailing slash. Remember, we could have a tag like
, so + // any later token processing scripts must convert improperly + // classified EmptyTags from StartTags. + $is_self_closing= (strpos($segment,'/') === strlen($segment)-1); + if ($is_self_closing) { + $segment = substr($segment, 0, strlen($segment) - 1); + } + + // Check if there are any attributes + $position_first_space = $this->nextWhiteSpace($segment); + if ($position_first_space === false) { + if ($is_self_closing) { + $array[] = new HTMLPurifier_Token_Empty($segment); + } else { + $array[] = new HTMLPurifier_Token_Start($segment); + } + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Grab out all the data + $type = substr($segment, 0, $position_first_space); + $attribute_string = + trim( + substr( + $segment, $position_first_space + ) + ); + if ($attribute_string) { + $attributes = $this->tokenizeAttributeString( + $attribute_string + ); + } else { + $attributes = array(); + } + + if ($is_self_closing) { + $array[] = new HTMLPurifier_Token_Empty($type, $attributes); + } else { + $array[] = new HTMLPurifier_Token_Start($type, $attributes); + } + $cursor = $position_next_gt + 1; + $inside_tag = false; + continue; + } else { + $array[] = new + HTMLPurifier_Token_Text( + '<' . + html_entity_decode( + substr($string, $cursor), + ENT_QUOTES + ) + ); + break; + } + break; + } + return $array; + } + + function tokenizeAttributeString($string) { + $string = (string) $string; // quick typecast + + if ($string == '') return array(); // no attributes + + // let's see if we can abort as quickly as possible + // one equal sign, no spaces => one attribute + $num_equal = substr_count($string, '='); + $has_space = strpos($string, ' '); + if ($num_equal === 0 && !$has_space) { + // bool attribute + return array($string => $string); + } elseif ($num_equal === 1 && !$has_space) { + // only one attribute + list($key, $quoted_value) = explode('=', $string); + $quoted_value = trim($quoted_value); + if (!$key) return array(); + if (!$quoted_value) return array($key => ''); + $first_char = @$quoted_value[0]; + $last_char = @$quoted_value[strlen($quoted_value)-1]; + + $same_quote = ($first_char == $last_char); + $open_quote = ($first_char == '"' || $first_char == "'"); + + if ( $same_quote && $open_quote) { + // well behaved + $value = substr($quoted_value, 1, strlen($quoted_value) - 2); + } else { + // not well behaved + if ($open_quote) { + $value = substr($quoted_value, 1); + } else { + $value = $quoted_value; + } + } + return array($key => $value); + } + + // setup loop environment + $array = array(); // return assoc array of attributes + $cursor = 0; // current position in string (moves forward) + $size = strlen($string); // size of the string (stays the same) + + // if we have unquoted attributes, the parser expects a terminating + // space, so let's guarantee that there's always a terminating space. + $string .= ' '; + + // infinite loop protection + $loops = 0; + + while(true) { + + // infinite loop protection + if (++$loops > 1000) return array(); + + if ($cursor >= $size) { + break; + } + + $cursor += ($value = strspn($string, "\x20\x09\x0D\x0A", $cursor)); + + $position_next_space = $this->nextWhiteSpace($string, $cursor); + $position_next_equal = strpos($string, '=', $cursor); + + // grab the key + + $key_begin = $cursor; //we're currently at the start of the key + + // scroll past all characters that are the key (not whitespace or =) + $cursor += strcspn($string, "\x20\x09\x0D\x0A=", $cursor); + + $key_end = $cursor; // now at the end of the key + + $key = substr($string, $key_begin, $key_end - $key_begin); + + if (!$key) continue; // empty key + + // scroll past all whitespace + $cursor += strspn($string, "\x20\x09\x0D\x0A", $cursor); + + if ($cursor >= $size) { + $array[$key] = $key; + break; + } + + // if the next character is an equal sign, we've got a regular + // pair, otherwise, it's a bool attribute + $first_char = @$string[$cursor]; + + if ($first_char == '=') { + // key="value" + + $cursor++; + $cursor += strspn($string, "\x20\x09\x0D\x0A", $cursor); + + // we might be in front of a quote right now + + $char = @$string[$cursor]; + + if ($char == '"' || $char == "'") { + // it's quoted, end bound is $char + $cursor++; + $value_begin = $cursor; + $cursor = strpos($string, $char, $cursor); + $value_end = $cursor; + } else { + // it's not quoted, end bound is whitespace + $value_begin = $cursor; + $cursor += strcspn($string, "\x20\x09\x0D\x0A", $cursor); + $value_end = $cursor; + } + + $value = substr($string, $value_begin, $value_end - $value_begin); + $array[$key] = $value; + $cursor++; + + } else { + // boolattr + if ($key !== '') { + $array[$key] = $key; + } + + } + } + return $array; + } + +} + ?> \ No newline at end of file diff --git a/library/HTMLPurifier/Lexer/PEARSax3.php b/library/HTMLPurifier/Lexer/PEARSax3.php index 2ff8a342..c13dc377 100644 --- a/library/HTMLPurifier/Lexer/PEARSax3.php +++ b/library/HTMLPurifier/Lexer/PEARSax3.php @@ -1,58 +1,58 @@ -tokens = array(); - $parser=& new XML_HTMLSax3(); - $parser->set_object($this); - $parser->set_element_handler('openHandler','closeHandler'); - $parser->set_data_handler('dataHandler'); - $parser->set_escape_handler('escapeHandler'); - $parser->set_option('XML_OPTION_ENTITIES_PARSED', 1); - $parser->parse($html); - return $this->tokens; - } - - function openHandler(&$parser, $name, $attrs, $closed) { - if ($closed) { - $this->tokens[] = new HTMLPurifier_Token_Empty($name, $attrs); - } else { - $this->tokens[] = new HTMLPurifier_Token_Start($name, $attrs); - } - return true; - } - - function closeHandler(&$parser, $name) { - // HTMLSax3 seems to always send empty tags an extra close tag - // check and ignore if you see it: - // [TESTME] to make sure it doesn't overreach - if ($this->tokens[count($this->tokens)-1]->type == 'empty') { - return true; - } - $this->tokens[] = new HTMLPurifier_Token_End($name); - return true; - } - - function dataHandler(&$parser, $data) { - $this->tokens[] = new HTMLPurifier_Token_Text($data); - return true; - } - - function escapeHandler(&$parser, $data) { - if (strpos($data, '-') === 0) { - $this->tokens[] = new HTMLPurifier_Token_Comment($data); - } - return true; - } - -} - +tokens = array(); + $parser=& new XML_HTMLSax3(); + $parser->set_object($this); + $parser->set_element_handler('openHandler','closeHandler'); + $parser->set_data_handler('dataHandler'); + $parser->set_escape_handler('escapeHandler'); + $parser->set_option('XML_OPTION_ENTITIES_PARSED', 1); + $parser->parse($html); + return $this->tokens; + } + + function openHandler(&$parser, $name, $attrs, $closed) { + if ($closed) { + $this->tokens[] = new HTMLPurifier_Token_Empty($name, $attrs); + } else { + $this->tokens[] = new HTMLPurifier_Token_Start($name, $attrs); + } + return true; + } + + function closeHandler(&$parser, $name) { + // HTMLSax3 seems to always send empty tags an extra close tag + // check and ignore if you see it: + // [TESTME] to make sure it doesn't overreach + if ($this->tokens[count($this->tokens)-1]->type == 'empty') { + return true; + } + $this->tokens[] = new HTMLPurifier_Token_End($name); + return true; + } + + function dataHandler(&$parser, $data) { + $this->tokens[] = new HTMLPurifier_Token_Text($data); + return true; + } + + function escapeHandler(&$parser, $data) { + if (strpos($data, '-') === 0) { + $this->tokens[] = new HTMLPurifier_Token_Comment($data); + } + return true; + } + +} + ?> \ No newline at end of file diff --git a/library/HTMLPurifier/Token.php b/library/HTMLPurifier/Token.php index 750de175..6275ea5a 100644 --- a/library/HTMLPurifier/Token.php +++ b/library/HTMLPurifier/Token.php @@ -1,60 +1,60 @@ -name = ctype_lower($name) ? $name : strtolower($name); - $this->attributes = $attributes; - } -} - -// start CONCRETE ones - -class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag -{ - var $type = 'start'; -} - -class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag -{ - var $type = 'empty'; -} - -// accepts attributes even though it really can't, for optimization reasons -class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag -{ - var $type = 'end'; -} - -class HTMLPurifier_Token_Text extends HTMLPurifier_Token -{ - var $name = '#PCDATA'; - var $type = 'text'; - var $data; - var $is_whitespace = false; - function HTMLPurifier_Token_Text($data) { - $this->data = $data; - if (ctype_space($data)) $this->is_whitespace = true; - } - function append($text) { - return new HTMLPurifier_Token_Text($this->data . $text->data); - } -} - -class HTMLPurifier_Token_Comment extends HTMLPurifier_Token -{ - var $data; - var $type = 'comment'; - function HTMLPurifier_Token_Comment($data) { - $this->data = $data; - } -} - +name = ctype_lower($name) ? $name : strtolower($name); + $this->attributes = $attributes; + } +} + +// start CONCRETE ones + +class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag +{ + var $type = 'start'; +} + +class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag +{ + var $type = 'empty'; +} + +// accepts attributes even though it really can't, for optimization reasons +class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag +{ + var $type = 'end'; +} + +class HTMLPurifier_Token_Text extends HTMLPurifier_Token +{ + var $name = '#PCDATA'; + var $type = 'text'; + var $data; + var $is_whitespace = false; + function HTMLPurifier_Token_Text($data) { + $this->data = $data; + if (ctype_space($data)) $this->is_whitespace = true; + } + function append($text) { + return new HTMLPurifier_Token_Text($this->data . $text->data); + } +} + +class HTMLPurifier_Token_Comment extends HTMLPurifier_Token +{ + var $data; + var $type = 'comment'; + function HTMLPurifier_Token_Comment($data) { + $this->data = $data; + } +} + ?> \ No newline at end of file diff --git a/tests/HTMLPurifier/ChildDefTest.php b/tests/HTMLPurifier/ChildDefTest.php index a95589ab..193e6d11 100644 --- a/tests/HTMLPurifier/ChildDefTest.php +++ b/tests/HTMLPurifier/ChildDefTest.php @@ -1,132 +1,132 @@ -lex = HTMLPurifier_Lexer::create(); - $this->gen = new HTMLPurifier_Generator(); - parent::UnitTestCase(); - } - - function assertSeries($inputs, $expect, $def) { - foreach ($inputs as $i => $input) { - $tokens = $this->lex->tokenizeHTML($input); - $result = $def->validateChildren($tokens); - if (is_bool($expect[$i])) { - $this->assertIdentical($expect[$i], $result); - } else { - $result_html = $this->gen->generateFromTokens($result); - $this->assertEqual($expect[$i], $result_html); - paintIf($result_html, $result_html != $expect[$i]); - } - } - } - - function test_complex() { - - // the table definition - $def = new HTMLPurifier_ChildDef( - '(caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))'); - - $inputs[0] = ''; - $expect[0] = false; - - // we really don't care what's inside, because if it turns out - // this tr is illegal, we'll end up re-evaluating the parent node - // anyway. - $inputs[1] = '
'; - $expect[1] = true; - - $inputs[2] = '' . - ''; - $expect[2] = true; - - $inputs[3] = ''; - $expect[3] = true; - - $this->assertSeries($inputs, $expect, $def); - - } - - function test_simple() { - - // simple is actually an abstract class - // but we're unit testing some of the conv. functions it gives - - $def = new HTMLPurifier_ChildDef_Simple('foobar | bang |gizmo'); - $this->assertEqual($def->elements, - array( - 'foobar' => true - ,'bang' => true - ,'gizmo' => true - )); - - $def = new HTMLPurifier_ChildDef_Simple(array('href', 'src')); - $this->assertEqual($def->elements, - array( - 'href' => true - ,'src' => true - )); - } - - function test_required_pcdata_forbidden() { - - $def = new HTMLPurifier_ChildDef_Required('dt | dd'); - - $inputs[0] = ''; - $expect[0] = false; - - $inputs[1] = '
Term
Text in an illegal location'. - '
Definition
Illegal tag'; - - $expect[1] = '
Term
Definition
'; - - $inputs[2] = 'How do you do!'; - $expect[2] = false; - - // whitespace shouldn't trigger it - $inputs[3] = "\n
Definition
"; - $expect[3] = true; - - $inputs[4] ='
Definition
'; - $expect[4] = '
Definition
'; - - $inputs[5] = "\t "; - $expect[5] = false; - - $this->assertSeries($inputs, $expect, $def); - - } - - function test_required_pcdata_allowed() { - $def = new HTMLPurifier_ChildDef_Required('#PCDATA | b'); - - $inputs[0] = 'Bold text'; - $expect[0] = 'Bold text<img />'; - - $this->assertSeries($inputs, $expect, $def); - } - - function test_optional() { - $def = new HTMLPurifier_ChildDef_Optional('b | i'); - - $inputs[0] = 'Bold text'; - $expect[0] = 'Bold text'; - - $inputs[1] = 'Not allowed text'; - $expect[1] = ''; - - $this->assertSeries($inputs, $expect, $def); - } - -} - +lex = HTMLPurifier_Lexer::create(); + $this->gen = new HTMLPurifier_Generator(); + parent::UnitTestCase(); + } + + function assertSeries($inputs, $expect, $def) { + foreach ($inputs as $i => $input) { + $tokens = $this->lex->tokenizeHTML($input); + $result = $def->validateChildren($tokens); + if (is_bool($expect[$i])) { + $this->assertIdentical($expect[$i], $result); + } else { + $result_html = $this->gen->generateFromTokens($result); + $this->assertEqual($expect[$i], $result_html); + paintIf($result_html, $result_html != $expect[$i]); + } + } + } + + function test_complex() { + + // the table definition + $def = new HTMLPurifier_ChildDef( + '(caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))'); + + $inputs[0] = ''; + $expect[0] = false; + + // we really don't care what's inside, because if it turns out + // this tr is illegal, we'll end up re-evaluating the parent node + // anyway. + $inputs[1] = '
'; + $expect[1] = true; + + $inputs[2] = '' . + ''; + $expect[2] = true; + + $inputs[3] = ''; + $expect[3] = true; + + $this->assertSeries($inputs, $expect, $def); + + } + + function test_simple() { + + // simple is actually an abstract class + // but we're unit testing some of the conv. functions it gives + + $def = new HTMLPurifier_ChildDef_Simple('foobar | bang |gizmo'); + $this->assertEqual($def->elements, + array( + 'foobar' => true + ,'bang' => true + ,'gizmo' => true + )); + + $def = new HTMLPurifier_ChildDef_Simple(array('href', 'src')); + $this->assertEqual($def->elements, + array( + 'href' => true + ,'src' => true + )); + } + + function test_required_pcdata_forbidden() { + + $def = new HTMLPurifier_ChildDef_Required('dt | dd'); + + $inputs[0] = ''; + $expect[0] = false; + + $inputs[1] = '
Term
Text in an illegal location'. + '
Definition
Illegal tag'; + + $expect[1] = '
Term
Definition
'; + + $inputs[2] = 'How do you do!'; + $expect[2] = false; + + // whitespace shouldn't trigger it + $inputs[3] = "\n
Definition
"; + $expect[3] = true; + + $inputs[4] ='
Definition
'; + $expect[4] = '
Definition
'; + + $inputs[5] = "\t "; + $expect[5] = false; + + $this->assertSeries($inputs, $expect, $def); + + } + + function test_required_pcdata_allowed() { + $def = new HTMLPurifier_ChildDef_Required('#PCDATA | b'); + + $inputs[0] = 'Bold text'; + $expect[0] = 'Bold text<img />'; + + $this->assertSeries($inputs, $expect, $def); + } + + function test_optional() { + $def = new HTMLPurifier_ChildDef_Optional('b | i'); + + $inputs[0] = 'Bold text'; + $expect[0] = 'Bold text'; + + $inputs[1] = 'Not allowed text'; + $expect[1] = ''; + + $this->assertSeries($inputs, $expect, $def); + } + +} + ?> \ No newline at end of file diff --git a/tests/HTMLPurifier/DefinitionTest.php b/tests/HTMLPurifier/DefinitionTest.php index 30dc24bc..9ac5893b 100644 --- a/tests/HTMLPurifier/DefinitionTest.php +++ b/tests/HTMLPurifier/DefinitionTest.php @@ -1,267 +1,267 @@ -UnitTestCase(); - $this->def = new HTMLPurifier_Definition(); - $this->def->loadData(); - $this->lex = new HTMLPurifier_Lexer(); - } - - function test_removeForeignElements() { - - $inputs = array(); - $expect = array(); - - $inputs[0] = array(); - $expect[0] = $inputs[0]; - - $inputs[1] = array( - new HTMLPurifier_Token_Text('This is ') - ,new HTMLPurifier_Token_Start('b', array()) - ,new HTMLPurifier_Token_Text('bold') - ,new HTMLPurifier_Token_End('b') - ,new HTMLPurifier_Token_Text(' text') - ); - $expect[1] = $inputs[1]; - - $inputs[2] = array( - new HTMLPurifier_Token_Start('asdf') - ,new HTMLPurifier_Token_End('asdf') - ,new HTMLPurifier_Token_Start('d', array('href' => 'bang!')) - ,new HTMLPurifier_Token_End('d') - ,new HTMLPurifier_Token_Start('pooloka') - ,new HTMLPurifier_Token_Start('poolasdf') - ,new HTMLPurifier_Token_Start('ds', array('moogle' => '&')) - ,new HTMLPurifier_Token_End('asdf') - ,new HTMLPurifier_Token_End('asdf') - ); - $expect[2] = array( - new HTMLPurifier_Token_Text('') - ,new HTMLPurifier_Token_Text('') - ,new HTMLPurifier_Token_Text('') - ,new HTMLPurifier_Token_Text('') - ,new HTMLPurifier_Token_Text('') - ,new HTMLPurifier_Token_Text('') - ,new HTMLPurifier_Token_Text('') - ,new HTMLPurifier_Token_Text('') - ,new HTMLPurifier_Token_Text('') - ); - - foreach ($inputs as $i => $input) { - $result = $this->def->removeForeignElements($input); - $this->assertEqual($expect[$i], $result); - paintIf($result, $result != $expect[$i]); - } - - } - - function test_makeWellFormed() { - - $inputs = array(); - $expect = array(); - - $inputs[0] = array(); - $expect[0] = $inputs[0]; - - $inputs[1] = array( - new HTMLPurifier_Token_Text('This is ') - ,new HTMLPurifier_Token_Start('b') - ,new HTMLPurifier_Token_Text('bold') - ,new HTMLPurifier_Token_End('b') - ,new HTMLPurifier_Token_Text(' text') - ,new HTMLPurifier_Token_Empty('br') - ); - $expect[1] = $inputs[1]; - - $inputs[2] = array( - new HTMLPurifier_Token_Start('b') - ,new HTMLPurifier_Token_Text('Unclosed tag, gasp!') - ); - $expect[2] = array( - new HTMLPurifier_Token_Start('b') - ,new HTMLPurifier_Token_Text('Unclosed tag, gasp!') - ,new HTMLPurifier_Token_End('b') - ); - - $inputs[3] = array( - new HTMLPurifier_Token_Start('b') - ,new HTMLPurifier_Token_Start('i') - ,new HTMLPurifier_Token_Text('The b is closed, but the i is not') - ,new HTMLPurifier_Token_End('b') - ); - $expect[3] = array( - new HTMLPurifier_Token_Start('b') - ,new HTMLPurifier_Token_Start('i') - ,new HTMLPurifier_Token_Text('The b is closed, but the i is not') - ,new HTMLPurifier_Token_End('i') - ,new HTMLPurifier_Token_End('b') - ); - - $inputs[4] = array( - new HTMLPurifier_Token_Text('Hey, recycle unused end tags!') - ,new HTMLPurifier_Token_End('b') - ); - $expect[4] = array( - new HTMLPurifier_Token_Text('Hey, recycle unused end tags!') - ,new HTMLPurifier_Token_Text('
') - ); - - $inputs[5] = array(new HTMLPurifier_Token_Start('br', array('style' => 'clear:both;'))); - $expect[5] = array(new HTMLPurifier_Token_Empty('br', array('style' => 'clear:both;'))); - - $inputs[6] = array(new HTMLPurifier_Token_Empty('div', array('style' => 'clear:both;'))); - $expect[6] = array( - new HTMLPurifier_Token_Start('div', array('style' => 'clear:both;')) - ,new HTMLPurifier_Token_End('div') - ); - - // test automatic paragraph closing - - $inputs[7] = array( - new HTMLPurifier_Token_Start('p') - ,new HTMLPurifier_Token_Text('Paragraph 1') - ,new HTMLPurifier_Token_Start('p') - ,new HTMLPurifier_Token_Text('Paragraph 2') - ); - $expect[7] = array( - new HTMLPurifier_Token_Start('p') - ,new HTMLPurifier_Token_Text('Paragraph 1') - ,new HTMLPurifier_Token_End('p') - ,new HTMLPurifier_Token_Start('p') - ,new HTMLPurifier_Token_Text('Paragraph 2') - ,new HTMLPurifier_Token_End('p') - ); - - $inputs[8] = array( - new HTMLPurifier_Token_Start('div') - ,new HTMLPurifier_Token_Start('p') - ,new HTMLPurifier_Token_Text('Paragraph 1 in a div') - ,new HTMLPurifier_Token_End('div') - ); - $expect[8] = array( - new HTMLPurifier_Token_Start('div') - ,new HTMLPurifier_Token_Start('p') - ,new HTMLPurifier_Token_Text('Paragraph 1 in a div') - ,new HTMLPurifier_Token_End('p') - ,new HTMLPurifier_Token_End('div') - ); - - // automatic list closing - - $inputs[9] = array( - new HTMLPurifier_Token_Start('ol') - - ,new HTMLPurifier_Token_Start('li') - ,new HTMLPurifier_Token_Text('Item 1') - - ,new HTMLPurifier_Token_Start('li') - ,new HTMLPurifier_Token_Text('Item 2') - - ,new HTMLPurifier_Token_End('ol') - ); - $expect[9] = array( - new HTMLPurifier_Token_Start('ol') - - ,new HTMLPurifier_Token_Start('li') - ,new HTMLPurifier_Token_Text('Item 1') - ,new HTMLPurifier_Token_End('li') - - ,new HTMLPurifier_Token_Start('li') - ,new HTMLPurifier_Token_Text('Item 2') - ,new HTMLPurifier_Token_End('li') - - ,new HTMLPurifier_Token_End('ol') - ); - - foreach ($inputs as $i => $input) { - $result = $this->def->makeWellFormed($input); - $this->assertEqual($expect[$i], $result); - paintIf($result, $result != $expect[$i]); - } - - } - - function test_fixNesting() { - $inputs = array(); - $expect = array(); - - // next id = 4 - - // legal inline nesting - $inputs[0] = array( - new HTMLPurifier_Token_Start('b'), - new HTMLPurifier_Token_Text('Bold text'), - new HTMLPurifier_Token_End('b'), - ); - $expect[0] = $inputs[0]; - - // legal inline and block - // as the parent element is considered FLOW - $inputs[1] = array( - new HTMLPurifier_Token_Start('a', array('href' => 'http://www.example.com/')), - new HTMLPurifier_Token_Text('Linky'), - new HTMLPurifier_Token_End('a'), - new HTMLPurifier_Token_Start('div'), - new HTMLPurifier_Token_Text('Block element'), - new HTMLPurifier_Token_End('div'), - ); - $expect[1] = $inputs[1]; - - // illegal block in inline, element -> text - $inputs[2] = array( - new HTMLPurifier_Token_Start('b'), - new HTMLPurifier_Token_Start('div'), - new HTMLPurifier_Token_Text('Illegal Div'), - new HTMLPurifier_Token_End('div'), - new HTMLPurifier_Token_End('b'), - ); - $expect[2] = array( - new HTMLPurifier_Token_Start('b'), - new HTMLPurifier_Token_Text('
'), - new HTMLPurifier_Token_Text('Illegal Div'), - new HTMLPurifier_Token_Text('
'), - new HTMLPurifier_Token_End('b'), - ); - - // test of empty set that's required, resulting in removal of node - $inputs[3] = array( - new HTMLPurifier_Token_Start('ul'), - new HTMLPurifier_Token_End('ul') - ); - $expect[3] = array(); - - // test illegal text which gets removed - $inputs[4] = array( - new HTMLPurifier_Token_Start('ul'), - new HTMLPurifier_Token_Text('Illegal Text'), - new HTMLPurifier_Token_Start('li'), - new HTMLPurifier_Token_Text('Legal item'), - new HTMLPurifier_Token_End('li'), - new HTMLPurifier_Token_End('ul') - ); - $expect[4] = array( - new HTMLPurifier_Token_Start('ul'), - new HTMLPurifier_Token_Start('li'), - new HTMLPurifier_Token_Text('Legal item'), - new HTMLPurifier_Token_End('li'), - new HTMLPurifier_Token_End('ul') - ); - - foreach ($inputs as $i => $input) { - $result = $this->def->fixNesting($input); - $this->assertEqual($expect[$i], $result); - paintIf($result, $result != $expect[$i]); - } - } - -} - +UnitTestCase(); + $this->def = new HTMLPurifier_Definition(); + $this->def->loadData(); + $this->lex = new HTMLPurifier_Lexer(); + } + + function test_removeForeignElements() { + + $inputs = array(); + $expect = array(); + + $inputs[0] = array(); + $expect[0] = $inputs[0]; + + $inputs[1] = array( + new HTMLPurifier_Token_Text('This is ') + ,new HTMLPurifier_Token_Start('b', array()) + ,new HTMLPurifier_Token_Text('bold') + ,new HTMLPurifier_Token_End('b') + ,new HTMLPurifier_Token_Text(' text') + ); + $expect[1] = $inputs[1]; + + $inputs[2] = array( + new HTMLPurifier_Token_Start('asdf') + ,new HTMLPurifier_Token_End('asdf') + ,new HTMLPurifier_Token_Start('d', array('href' => 'bang!')) + ,new HTMLPurifier_Token_End('d') + ,new HTMLPurifier_Token_Start('pooloka') + ,new HTMLPurifier_Token_Start('poolasdf') + ,new HTMLPurifier_Token_Start('ds', array('moogle' => '&')) + ,new HTMLPurifier_Token_End('asdf') + ,new HTMLPurifier_Token_End('asdf') + ); + $expect[2] = array( + new HTMLPurifier_Token_Text('') + ,new HTMLPurifier_Token_Text('') + ,new HTMLPurifier_Token_Text('') + ,new HTMLPurifier_Token_Text('') + ,new HTMLPurifier_Token_Text('') + ,new HTMLPurifier_Token_Text('') + ,new HTMLPurifier_Token_Text('') + ,new HTMLPurifier_Token_Text('') + ,new HTMLPurifier_Token_Text('') + ); + + foreach ($inputs as $i => $input) { + $result = $this->def->removeForeignElements($input); + $this->assertEqual($expect[$i], $result); + paintIf($result, $result != $expect[$i]); + } + + } + + function test_makeWellFormed() { + + $inputs = array(); + $expect = array(); + + $inputs[0] = array(); + $expect[0] = $inputs[0]; + + $inputs[1] = array( + new HTMLPurifier_Token_Text('This is ') + ,new HTMLPurifier_Token_Start('b') + ,new HTMLPurifier_Token_Text('bold') + ,new HTMLPurifier_Token_End('b') + ,new HTMLPurifier_Token_Text(' text') + ,new HTMLPurifier_Token_Empty('br') + ); + $expect[1] = $inputs[1]; + + $inputs[2] = array( + new HTMLPurifier_Token_Start('b') + ,new HTMLPurifier_Token_Text('Unclosed tag, gasp!') + ); + $expect[2] = array( + new HTMLPurifier_Token_Start('b') + ,new HTMLPurifier_Token_Text('Unclosed tag, gasp!') + ,new HTMLPurifier_Token_End('b') + ); + + $inputs[3] = array( + new HTMLPurifier_Token_Start('b') + ,new HTMLPurifier_Token_Start('i') + ,new HTMLPurifier_Token_Text('The b is closed, but the i is not') + ,new HTMLPurifier_Token_End('b') + ); + $expect[3] = array( + new HTMLPurifier_Token_Start('b') + ,new HTMLPurifier_Token_Start('i') + ,new HTMLPurifier_Token_Text('The b is closed, but the i is not') + ,new HTMLPurifier_Token_End('i') + ,new HTMLPurifier_Token_End('b') + ); + + $inputs[4] = array( + new HTMLPurifier_Token_Text('Hey, recycle unused end tags!') + ,new HTMLPurifier_Token_End('b') + ); + $expect[4] = array( + new HTMLPurifier_Token_Text('Hey, recycle unused end tags!') + ,new HTMLPurifier_Token_Text('') + ); + + $inputs[5] = array(new HTMLPurifier_Token_Start('br', array('style' => 'clear:both;'))); + $expect[5] = array(new HTMLPurifier_Token_Empty('br', array('style' => 'clear:both;'))); + + $inputs[6] = array(new HTMLPurifier_Token_Empty('div', array('style' => 'clear:both;'))); + $expect[6] = array( + new HTMLPurifier_Token_Start('div', array('style' => 'clear:both;')) + ,new HTMLPurifier_Token_End('div') + ); + + // test automatic paragraph closing + + $inputs[7] = array( + new HTMLPurifier_Token_Start('p') + ,new HTMLPurifier_Token_Text('Paragraph 1') + ,new HTMLPurifier_Token_Start('p') + ,new HTMLPurifier_Token_Text('Paragraph 2') + ); + $expect[7] = array( + new HTMLPurifier_Token_Start('p') + ,new HTMLPurifier_Token_Text('Paragraph 1') + ,new HTMLPurifier_Token_End('p') + ,new HTMLPurifier_Token_Start('p') + ,new HTMLPurifier_Token_Text('Paragraph 2') + ,new HTMLPurifier_Token_End('p') + ); + + $inputs[8] = array( + new HTMLPurifier_Token_Start('div') + ,new HTMLPurifier_Token_Start('p') + ,new HTMLPurifier_Token_Text('Paragraph 1 in a div') + ,new HTMLPurifier_Token_End('div') + ); + $expect[8] = array( + new HTMLPurifier_Token_Start('div') + ,new HTMLPurifier_Token_Start('p') + ,new HTMLPurifier_Token_Text('Paragraph 1 in a div') + ,new HTMLPurifier_Token_End('p') + ,new HTMLPurifier_Token_End('div') + ); + + // automatic list closing + + $inputs[9] = array( + new HTMLPurifier_Token_Start('ol') + + ,new HTMLPurifier_Token_Start('li') + ,new HTMLPurifier_Token_Text('Item 1') + + ,new HTMLPurifier_Token_Start('li') + ,new HTMLPurifier_Token_Text('Item 2') + + ,new HTMLPurifier_Token_End('ol') + ); + $expect[9] = array( + new HTMLPurifier_Token_Start('ol') + + ,new HTMLPurifier_Token_Start('li') + ,new HTMLPurifier_Token_Text('Item 1') + ,new HTMLPurifier_Token_End('li') + + ,new HTMLPurifier_Token_Start('li') + ,new HTMLPurifier_Token_Text('Item 2') + ,new HTMLPurifier_Token_End('li') + + ,new HTMLPurifier_Token_End('ol') + ); + + foreach ($inputs as $i => $input) { + $result = $this->def->makeWellFormed($input); + $this->assertEqual($expect[$i], $result); + paintIf($result, $result != $expect[$i]); + } + + } + + function test_fixNesting() { + $inputs = array(); + $expect = array(); + + // next id = 4 + + // legal inline nesting + $inputs[0] = array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('Bold text'), + new HTMLPurifier_Token_End('b'), + ); + $expect[0] = $inputs[0]; + + // legal inline and block + // as the parent element is considered FLOW + $inputs[1] = array( + new HTMLPurifier_Token_Start('a', array('href' => 'http://www.example.com/')), + new HTMLPurifier_Token_Text('Linky'), + new HTMLPurifier_Token_End('a'), + new HTMLPurifier_Token_Start('div'), + new HTMLPurifier_Token_Text('Block element'), + new HTMLPurifier_Token_End('div'), + ); + $expect[1] = $inputs[1]; + + // illegal block in inline, element -> text + $inputs[2] = array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Start('div'), + new HTMLPurifier_Token_Text('Illegal Div'), + new HTMLPurifier_Token_End('div'), + new HTMLPurifier_Token_End('b'), + ); + $expect[2] = array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('
'), + new HTMLPurifier_Token_Text('Illegal Div'), + new HTMLPurifier_Token_Text('
'), + new HTMLPurifier_Token_End('b'), + ); + + // test of empty set that's required, resulting in removal of node + $inputs[3] = array( + new HTMLPurifier_Token_Start('ul'), + new HTMLPurifier_Token_End('ul') + ); + $expect[3] = array(); + + // test illegal text which gets removed + $inputs[4] = array( + new HTMLPurifier_Token_Start('ul'), + new HTMLPurifier_Token_Text('Illegal Text'), + new HTMLPurifier_Token_Start('li'), + new HTMLPurifier_Token_Text('Legal item'), + new HTMLPurifier_Token_End('li'), + new HTMLPurifier_Token_End('ul') + ); + $expect[4] = array( + new HTMLPurifier_Token_Start('ul'), + new HTMLPurifier_Token_Start('li'), + new HTMLPurifier_Token_Text('Legal item'), + new HTMLPurifier_Token_End('li'), + new HTMLPurifier_Token_End('ul') + ); + + foreach ($inputs as $i => $input) { + $result = $this->def->fixNesting($input); + $this->assertEqual($expect[$i], $result); + paintIf($result, $result != $expect[$i]); + } + } + +} + ?> \ No newline at end of file diff --git a/tests/HTMLPurifier/GeneratorTest.php b/tests/HTMLPurifier/GeneratorTest.php index f39280ef..ac1bb0c1 100644 --- a/tests/HTMLPurifier/GeneratorTest.php +++ b/tests/HTMLPurifier/GeneratorTest.php @@ -1,89 +1,89 @@ -UnitTestCase(); - $this->gen = new HTMLPurifier_Generator(); - } - - function test_generateFromToken() { - - $inputs = array(); - $expect = array(); - - $inputs[0] = new HTMLPurifier_Token_Text('Foobar.<>'); - $expect[0] = 'Foobar.<>'; - - $inputs[1] = new HTMLPurifier_Token_Start('a', - array('href' => 'dyn?a=foo&b=bar') - ); - $expect[1] = '
'; - - $inputs[2] = new HTMLPurifier_Token_End('b'); - $expect[2] = ''; - - $inputs[3] = new HTMLPurifier_Token_Empty('br', - array('style' => 'font-family:"Courier New";') - ); - $expect[3] = '
'; - - $inputs[4] = new HTMLPurifier_Token_Start('asdf'); - $expect[4] = ''; - - $inputs[5] = new HTMLPurifier_Token_Empty('br'); - $expect[5] = '
'; - - foreach ($inputs as $i => $input) { - $result = $this->gen->generateFromToken($input); - $this->assertEqual($result, $expect[$i]); - paintIf($result, $result != $expect[$i]); - } - - } - - function test_generateAttributes() { - - $inputs = array(); - $expect = array(); - - $inputs[0] = array(); - $expect[0] = ''; - - $inputs[1] = array('href' => 'dyn?a=foo&b=bar'); - $expect[1] = 'href="dyn?a=foo&b=bar"'; - - $inputs[2] = array('style' => 'font-family:"Courier New";'); - $expect[2] = 'style="font-family:"Courier New";"'; - - $inputs[3] = array('src' => 'picture.jpg', 'alt' => 'Short & interesting'); - $expect[3] = 'src="picture.jpg" alt="Short & interesting"'; - - foreach ($inputs as $i => $input) { - $result = $this->gen->generateAttributes($input); - $this->assertEqual($result, $expect[$i]); - paintIf($result, $result != $expect[$i]); - } - - } - - function test_generateFromTokens() { - - $tokens = array( - new HTMLPurifier_Token_Start('b'), - new HTMLPurifier_Token_Text('Foobar!'), - new HTMLPurifier_Token_End('b') - ); - $expect = 'Foobar!'; - $this->assertEqual($expect, $this->gen->generateFromTokens($tokens)); - - } - -} - +UnitTestCase(); + $this->gen = new HTMLPurifier_Generator(); + } + + function test_generateFromToken() { + + $inputs = array(); + $expect = array(); + + $inputs[0] = new HTMLPurifier_Token_Text('Foobar.<>'); + $expect[0] = 'Foobar.<>'; + + $inputs[1] = new HTMLPurifier_Token_Start('a', + array('href' => 'dyn?a=foo&b=bar') + ); + $expect[1] = '
'; + + $inputs[2] = new HTMLPurifier_Token_End('b'); + $expect[2] = ''; + + $inputs[3] = new HTMLPurifier_Token_Empty('br', + array('style' => 'font-family:"Courier New";') + ); + $expect[3] = '
'; + + $inputs[4] = new HTMLPurifier_Token_Start('asdf'); + $expect[4] = ''; + + $inputs[5] = new HTMLPurifier_Token_Empty('br'); + $expect[5] = '
'; + + foreach ($inputs as $i => $input) { + $result = $this->gen->generateFromToken($input); + $this->assertEqual($result, $expect[$i]); + paintIf($result, $result != $expect[$i]); + } + + } + + function test_generateAttributes() { + + $inputs = array(); + $expect = array(); + + $inputs[0] = array(); + $expect[0] = ''; + + $inputs[1] = array('href' => 'dyn?a=foo&b=bar'); + $expect[1] = 'href="dyn?a=foo&b=bar"'; + + $inputs[2] = array('style' => 'font-family:"Courier New";'); + $expect[2] = 'style="font-family:"Courier New";"'; + + $inputs[3] = array('src' => 'picture.jpg', 'alt' => 'Short & interesting'); + $expect[3] = 'src="picture.jpg" alt="Short & interesting"'; + + foreach ($inputs as $i => $input) { + $result = $this->gen->generateAttributes($input); + $this->assertEqual($result, $expect[$i]); + paintIf($result, $result != $expect[$i]); + } + + } + + function test_generateFromTokens() { + + $tokens = array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('Foobar!'), + new HTMLPurifier_Token_End('b') + ); + $expect = 'Foobar!'; + $this->assertEqual($expect, $this->gen->generateFromTokens($tokens)); + + } + +} + ?> \ No newline at end of file diff --git a/tests/HTMLPurifier/Lexer/DirectLexTest.php b/tests/HTMLPurifier/Lexer/DirectLexTest.php index 786e4be6..f2fe5b1a 100644 --- a/tests/HTMLPurifier/Lexer/DirectLexTest.php +++ b/tests/HTMLPurifier/Lexer/DirectLexTest.php @@ -1,83 +1,83 @@ -DirectLex = new HTMLPurifier_Lexer_DirectLex(); - } - - function test_nextWhiteSpace() { - $HP =& $this->DirectLex; - $this->assertIdentical(false, $HP->nextWhiteSpace('asdf')); - $this->assertIdentical(0, $HP->nextWhiteSpace(' asdf')); - $this->assertIdentical(0, $HP->nextWhiteSpace("\nasdf")); - $this->assertIdentical(1, $HP->nextWhiteSpace("a\tsdf")); - $this->assertIdentical(4, $HP->nextWhiteSpace("asdf\r")); - $this->assertIdentical(2, $HP->nextWhiteSpace("as\t\r\nasdf as")); - $this->assertIdentical(3, $HP->nextWhiteSpace('a a ', 2)); - } - - function test_parseData() { - $HP =& $this->DirectLex; - $this->assertIdentical('asdf', $HP->parseData('asdf')); - $this->assertIdentical('&', $HP->parseData('&')); - $this->assertIdentical('"', $HP->parseData('"')); - $this->assertIdentical("'", $HP->parseData(''')); - $this->assertIdentical('-', $HP->parseData('-')); - // UTF-8 needed!!! - } - - // internals testing - function test_tokenizeAttributeString() { - - $input[0] = 'href="asdf" boom="assdf"'; - $expect[0] = array('href'=>'asdf', 'boom'=>'assdf'); - - $input[1] = "href='r'"; - $expect[1] = array('href'=>'r'); - - $input[2] = 'onclick="javascript:alert(\'asdf\');"'; - $expect[2] = array('onclick' => "javascript:alert('asdf');"); - - $input[3] = 'selected'; - $expect[3] = array('selected'=>'selected'); - - $input[4] = '="asdf"'; - $expect[4] = array(); - - $input[5] = 'missile=launch'; - $expect[5] = array('missile' => 'launch'); - - $input[6] = 'href="foo'; - $expect[6] = array('href' => 'foo'); - - $input[7] = '"='; - $expect[7] = array('"' => ''); - - $input[8] = 'href ="about:blank"rel ="nofollow"'; - $expect[8] = array('href' => 'about:blank', 'rel' => 'nofollow'); - - $input[9] = 'foo bar'; - $expect[9] = array('foo' => 'foo', 'bar' => 'bar'); - - $input[10] = 'foo="bar" blue'; - $expect[10] = array('foo' => 'bar', 'blue' => 'blue'); - - $size = count($input); - for($i = 0; $i < $size; $i++) { - $result = $this->DirectLex->tokenizeAttributeString($input[$i]); - $this->assertEqual($expect[$i], $result, 'Test ' . $i . ': %s'); - paintIf($result, $expect[$i] != $result); - } - - } - - -} - +DirectLex = new HTMLPurifier_Lexer_DirectLex(); + } + + function test_nextWhiteSpace() { + $HP =& $this->DirectLex; + $this->assertIdentical(false, $HP->nextWhiteSpace('asdf')); + $this->assertIdentical(0, $HP->nextWhiteSpace(' asdf')); + $this->assertIdentical(0, $HP->nextWhiteSpace("\nasdf")); + $this->assertIdentical(1, $HP->nextWhiteSpace("a\tsdf")); + $this->assertIdentical(4, $HP->nextWhiteSpace("asdf\r")); + $this->assertIdentical(2, $HP->nextWhiteSpace("as\t\r\nasdf as")); + $this->assertIdentical(3, $HP->nextWhiteSpace('a a ', 2)); + } + + function test_parseData() { + $HP =& $this->DirectLex; + $this->assertIdentical('asdf', $HP->parseData('asdf')); + $this->assertIdentical('&', $HP->parseData('&')); + $this->assertIdentical('"', $HP->parseData('"')); + $this->assertIdentical("'", $HP->parseData(''')); + $this->assertIdentical('-', $HP->parseData('-')); + // UTF-8 needed!!! + } + + // internals testing + function test_tokenizeAttributeString() { + + $input[0] = 'href="asdf" boom="assdf"'; + $expect[0] = array('href'=>'asdf', 'boom'=>'assdf'); + + $input[1] = "href='r'"; + $expect[1] = array('href'=>'r'); + + $input[2] = 'onclick="javascript:alert(\'asdf\');"'; + $expect[2] = array('onclick' => "javascript:alert('asdf');"); + + $input[3] = 'selected'; + $expect[3] = array('selected'=>'selected'); + + $input[4] = '="asdf"'; + $expect[4] = array(); + + $input[5] = 'missile=launch'; + $expect[5] = array('missile' => 'launch'); + + $input[6] = 'href="foo'; + $expect[6] = array('href' => 'foo'); + + $input[7] = '"='; + $expect[7] = array('"' => ''); + + $input[8] = 'href ="about:blank"rel ="nofollow"'; + $expect[8] = array('href' => 'about:blank', 'rel' => 'nofollow'); + + $input[9] = 'foo bar'; + $expect[9] = array('foo' => 'foo', 'bar' => 'bar'); + + $input[10] = 'foo="bar" blue'; + $expect[10] = array('foo' => 'bar', 'blue' => 'blue'); + + $size = count($input); + for($i = 0; $i < $size; $i++) { + $result = $this->DirectLex->tokenizeAttributeString($input[$i]); + $this->assertEqual($expect[$i], $result, 'Test ' . $i . ': %s'); + paintIf($result, $expect[$i] != $result); + } + + } + + +} + ?> \ No newline at end of file diff --git a/tests/HTMLPurifier/LexerTest.php b/tests/HTMLPurifier/LexerTest.php index 3388a9b7..2693b825 100644 --- a/tests/HTMLPurifier/LexerTest.php +++ b/tests/HTMLPurifier/LexerTest.php @@ -1,197 +1,197 @@ -DirectLex = new HTMLPurifier_Lexer_DirectLex(); - $this->PEARSax3 = new HTMLPurifier_Lexer_PEARSax3(); - - $this->_has_dom = version_compare(PHP_VERSION, '5', '>='); - - if ($this->_has_dom) { - require_once 'HTMLPurifier/Lexer/DOMLex.php'; - $this->DOMLex = new HTMLPurifier_Lexer_DOMLex(); - } - - } - - function test_tokenizeHTML() { - - $input = array(); - $expect = array(); - $sax_expect = array(); - - $input[0] = ''; - $expect[0] = array(); - - $input[1] = 'This is regular text.'; - $expect[1] = array( - new HTMLPurifier_Token_Text('This is regular text.') - ); - - $input[2] = 'This is bold text'; - $expect[2] = array( - new HTMLPurifier_Token_Text('This is ') - ,new HTMLPurifier_Token_Start('b', array()) - ,new HTMLPurifier_Token_Text('bold') - ,new HTMLPurifier_Token_End('b') - ,new HTMLPurifier_Token_Text(' text') - ); - - $input[3] = '
Totally rad dude. asdf
'; - $expect[3] = array( - new HTMLPurifier_Token_Start('DIV', array()) - ,new HTMLPurifier_Token_Text('Totally rad dude. ') - ,new HTMLPurifier_Token_Start('b', array()) - ,new HTMLPurifier_Token_Text('asdf') - ,new HTMLPurifier_Token_End('b') - ,new HTMLPurifier_Token_End('div') - ); - - // [XML-INVALID] - $input[4] = '
'; - $expect[4] = array( - new HTMLPurifier_Token_Start('asdf') - ,new HTMLPurifier_Token_End('asdf') - ,new HTMLPurifier_Token_Start('d') - ,new HTMLPurifier_Token_End('d') - ,new HTMLPurifier_Token_Start('poOloka') - ,new HTMLPurifier_Token_Start('poolasdf') - ,new HTMLPurifier_Token_Start('ds') - ,new HTMLPurifier_Token_End('asdf') - ,new HTMLPurifier_Token_End('ASDF') - ); - // DOM is different because it condenses empty tags into REAL empty ones - // as well as makes it well-formed - $dom_expect[4] = array( - new HTMLPurifier_Token_Empty('asdf') - ,new HTMLPurifier_Token_Empty('d') - ,new HTMLPurifier_Token_Start('pooloka') - ,new HTMLPurifier_Token_Start('poolasdf') - ,new HTMLPurifier_Token_Empty('ds') - ,new HTMLPurifier_Token_End('poolasdf') - ,new HTMLPurifier_Token_End('pooloka') - ); - - $input[5] = 'Link to foobar
'; - $expect[5] = array( - new HTMLPurifier_Token_Start('a',array('href'=>'foobar.php','title'=>'foo!')) - ,new HTMLPurifier_Token_Text('Link to ') - ,new HTMLPurifier_Token_Start('b',array('id'=>'asdf')) - ,new HTMLPurifier_Token_Text('foobar') - ,new HTMLPurifier_Token_End('b') - ,new HTMLPurifier_Token_End('a') - ); - - $input[6] = '
'; - $expect[6] = array( - new HTMLPurifier_Token_Empty('br') - ); - - // [SGML-INVALID] [RECOVERABLE] - $input[7] = ' '; - $expect[7] = array( - new HTMLPurifier_Token_Comment(' Comment ') - ,new HTMLPurifier_Token_Text(' ') - ,new HTMLPurifier_Token_Comment(' not so well formed -') - ); - $sax_expect[7] = false; // we need to figure out proper comment output - - // [SGML-INVALID] - $input[8] = ''')) - ); - // DOM parses it into an empty tag - $dom_expect[8] = array( - new HTMLPurifier_Token_Empty('a', array('href'=>'')) - ); - - $input[9] = '<b>'; - $expect[9] = array( - new HTMLPurifier_Token_Text('') - ); - $sax_expect[9] = array( - new HTMLPurifier_Token_Text('<') - ,new HTMLPurifier_Token_Text('b') - ,new HTMLPurifier_Token_Text('>') - ); - // note that SAX can clump text nodes together. We won't be - // too picky though - - // [SGML-INVALID] - $input[10] = ''; - // We barf on this, aim for no attributes - $expect[10] = array( - new HTMLPurifier_Token_Start('a', array('"' => '')) - ); - // DOM correctly has no attributes, but also closes the tag - $dom_expect[10] = array( - new HTMLPurifier_Token_Empty('a') - ); - // SAX barfs on this - $sax_expect[10] = array( - new HTMLPurifier_Token_Start('a', array('"' => '')) - ); - - // [INVALID] [RECOVERABLE] - $input[11] = '"'; - $expect[11] = array( new HTMLPurifier_Token_Text('"') ); - - // compare with this valid one: - $input[12] = '"'; - $expect[12] = array( new HTMLPurifier_Token_Text('"') ); - $sax_expect[12] = false; - // SAX chokes on this? We do have entity parsing on, so it should work! - - foreach($input as $i => $discard) { - $result = $this->DirectLex->tokenizeHTML($input[$i]); - $this->assertEqual($expect[$i], $result, 'Test '.$i.': %s'); - paintIf($result, $expect[$i] != $result); - - // assert unless I say otherwise - $sax_result = $this->PEARSax3->tokenizeHTML($input[$i]); - if (!isset($sax_expect[$i])) { - // by default, assert with normal result - $this->assertEqual($expect[$i], $sax_result, 'Test '.$i.': %s'); - paintIf($sax_result, $expect[$i] != $sax_result); - } elseif ($sax_expect[$i] === false) { - // assertions were turned off, optionally dump - // paintIf($sax_expect, $i == NUMBER); - } else { - // match with a custom SAX result array - $this->assertEqual($sax_expect[$i], $sax_result, 'Test '.$i.': %s'); - paintIf($sax_result, $sax_expect[$i] != $sax_result); - } - if ($this->_has_dom) { - $dom_result = $this->DOMLex->tokenizeHTML($input[$i]); - // same structure as SAX - if (!isset($dom_expect[$i])) { - $this->assertEqual($expect[$i], $dom_result, 'Test '.$i.': %s'); - paintIf($dom_result, $expect[$i] != $dom_result); - } elseif ($dom_expect[$i] === false) { - // paintIf($dom_result, $i == NUMBER); - } else { - $this->assertEqual($dom_expect[$i], $dom_result, 'Test '.$i.': %s'); - paintIf($dom_result, $dom_expect[$i] != $dom_result); - } - } - - } - - } - -} - +DirectLex = new HTMLPurifier_Lexer_DirectLex(); + $this->PEARSax3 = new HTMLPurifier_Lexer_PEARSax3(); + + $this->_has_dom = version_compare(PHP_VERSION, '5', '>='); + + if ($this->_has_dom) { + require_once 'HTMLPurifier/Lexer/DOMLex.php'; + $this->DOMLex = new HTMLPurifier_Lexer_DOMLex(); + } + + } + + function test_tokenizeHTML() { + + $input = array(); + $expect = array(); + $sax_expect = array(); + + $input[0] = ''; + $expect[0] = array(); + + $input[1] = 'This is regular text.'; + $expect[1] = array( + new HTMLPurifier_Token_Text('This is regular text.') + ); + + $input[2] = 'This is bold text'; + $expect[2] = array( + new HTMLPurifier_Token_Text('This is ') + ,new HTMLPurifier_Token_Start('b', array()) + ,new HTMLPurifier_Token_Text('bold') + ,new HTMLPurifier_Token_End('b') + ,new HTMLPurifier_Token_Text(' text') + ); + + $input[3] = '
Totally rad dude. asdf
'; + $expect[3] = array( + new HTMLPurifier_Token_Start('DIV', array()) + ,new HTMLPurifier_Token_Text('Totally rad dude. ') + ,new HTMLPurifier_Token_Start('b', array()) + ,new HTMLPurifier_Token_Text('asdf') + ,new HTMLPurifier_Token_End('b') + ,new HTMLPurifier_Token_End('div') + ); + + // [XML-INVALID] + $input[4] = ''; + $expect[4] = array( + new HTMLPurifier_Token_Start('asdf') + ,new HTMLPurifier_Token_End('asdf') + ,new HTMLPurifier_Token_Start('d') + ,new HTMLPurifier_Token_End('d') + ,new HTMLPurifier_Token_Start('poOloka') + ,new HTMLPurifier_Token_Start('poolasdf') + ,new HTMLPurifier_Token_Start('ds') + ,new HTMLPurifier_Token_End('asdf') + ,new HTMLPurifier_Token_End('ASDF') + ); + // DOM is different because it condenses empty tags into REAL empty ones + // as well as makes it well-formed + $dom_expect[4] = array( + new HTMLPurifier_Token_Empty('asdf') + ,new HTMLPurifier_Token_Empty('d') + ,new HTMLPurifier_Token_Start('pooloka') + ,new HTMLPurifier_Token_Start('poolasdf') + ,new HTMLPurifier_Token_Empty('ds') + ,new HTMLPurifier_Token_End('poolasdf') + ,new HTMLPurifier_Token_End('pooloka') + ); + + $input[5] = 'Link to foobar
'; + $expect[5] = array( + new HTMLPurifier_Token_Start('a',array('href'=>'foobar.php','title'=>'foo!')) + ,new HTMLPurifier_Token_Text('Link to ') + ,new HTMLPurifier_Token_Start('b',array('id'=>'asdf')) + ,new HTMLPurifier_Token_Text('foobar') + ,new HTMLPurifier_Token_End('b') + ,new HTMLPurifier_Token_End('a') + ); + + $input[6] = '
'; + $expect[6] = array( + new HTMLPurifier_Token_Empty('br') + ); + + // [SGML-INVALID] [RECOVERABLE] + $input[7] = ' '; + $expect[7] = array( + new HTMLPurifier_Token_Comment(' Comment ') + ,new HTMLPurifier_Token_Text(' ') + ,new HTMLPurifier_Token_Comment(' not so well formed -') + ); + $sax_expect[7] = false; // we need to figure out proper comment output + + // [SGML-INVALID] + $input[8] = ''')) + ); + // DOM parses it into an empty tag + $dom_expect[8] = array( + new HTMLPurifier_Token_Empty('a', array('href'=>'')) + ); + + $input[9] = '<b>'; + $expect[9] = array( + new HTMLPurifier_Token_Text('') + ); + $sax_expect[9] = array( + new HTMLPurifier_Token_Text('<') + ,new HTMLPurifier_Token_Text('b') + ,new HTMLPurifier_Token_Text('>') + ); + // note that SAX can clump text nodes together. We won't be + // too picky though + + // [SGML-INVALID] + $input[10] = ''; + // We barf on this, aim for no attributes + $expect[10] = array( + new HTMLPurifier_Token_Start('a', array('"' => '')) + ); + // DOM correctly has no attributes, but also closes the tag + $dom_expect[10] = array( + new HTMLPurifier_Token_Empty('a') + ); + // SAX barfs on this + $sax_expect[10] = array( + new HTMLPurifier_Token_Start('a', array('"' => '')) + ); + + // [INVALID] [RECOVERABLE] + $input[11] = '"'; + $expect[11] = array( new HTMLPurifier_Token_Text('"') ); + + // compare with this valid one: + $input[12] = '"'; + $expect[12] = array( new HTMLPurifier_Token_Text('"') ); + $sax_expect[12] = false; + // SAX chokes on this? We do have entity parsing on, so it should work! + + foreach($input as $i => $discard) { + $result = $this->DirectLex->tokenizeHTML($input[$i]); + $this->assertEqual($expect[$i], $result, 'Test '.$i.': %s'); + paintIf($result, $expect[$i] != $result); + + // assert unless I say otherwise + $sax_result = $this->PEARSax3->tokenizeHTML($input[$i]); + if (!isset($sax_expect[$i])) { + // by default, assert with normal result + $this->assertEqual($expect[$i], $sax_result, 'Test '.$i.': %s'); + paintIf($sax_result, $expect[$i] != $sax_result); + } elseif ($sax_expect[$i] === false) { + // assertions were turned off, optionally dump + // paintIf($sax_expect, $i == NUMBER); + } else { + // match with a custom SAX result array + $this->assertEqual($sax_expect[$i], $sax_result, 'Test '.$i.': %s'); + paintIf($sax_result, $sax_expect[$i] != $sax_result); + } + if ($this->_has_dom) { + $dom_result = $this->DOMLex->tokenizeHTML($input[$i]); + // same structure as SAX + if (!isset($dom_expect[$i])) { + $this->assertEqual($expect[$i], $dom_result, 'Test '.$i.': %s'); + paintIf($dom_result, $expect[$i] != $dom_result); + } elseif ($dom_expect[$i] === false) { + // paintIf($dom_result, $i == NUMBER); + } else { + $this->assertEqual($dom_expect[$i], $dom_result, 'Test '.$i.': %s'); + paintIf($dom_result, $dom_expect[$i] != $dom_result); + } + } + + } + + } + +} + ?> \ No newline at end of file diff --git a/tests/index.php b/tests/index.php index 4586f098..593023ec 100644 --- a/tests/index.php +++ b/tests/index.php @@ -1,25 +1,25 @@ -addTestFile('HTMLPurifier/LexerTest.php'); -$test->addTestFile('HTMLPurifier/Lexer/DirectLexTest.php'); -//$test->addTestFile('TokenTest.php'); -$test->addTestFile('HTMLPurifier/DefinitionTest.php'); -$test->addTestFile('HTMLPurifier/ChildDefTest.php'); -$test->addTestFile('HTMLPurifier/GeneratorTest.php'); - -$test->run( new HtmlReporter() ); - +addTestFile('HTMLPurifier/LexerTest.php'); +$test->addTestFile('HTMLPurifier/Lexer/DirectLexTest.php'); +//$test->addTestFile('TokenTest.php'); +$test->addTestFile('HTMLPurifier/DefinitionTest.php'); +$test->addTestFile('HTMLPurifier/ChildDefTest.php'); +$test->addTestFile('HTMLPurifier/GeneratorTest.php'); + +$test->run( new HtmlReporter() ); + ?> \ No newline at end of file