Skip to content

Commit

Permalink
feat: add config entry attr_validation
Browse files Browse the repository at this point in the history
It is now possible to use PollFromText::ATTR_AS_* to validate attributes. Currently there are two options: STRIC_JSON (forces attribute string to be a valid json) and TEXT (allow anything).
  • Loading branch information
Dovyski committed Sep 13, 2021
1 parent 045ab60 commit e402484
Show file tree
Hide file tree
Showing 8 changed files with 527 additions and 64 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and the project follows [semantic versioning](http://semver.org/spec/v2.0.0.html).

## [1.1.0] (https://github.com/ccuffs/poll-from-text/releases/tag/v.1.1.0) - 2021-09-13
### Added
- Config entry `attr_validation` to control how attributes are validated.

## [1.0.1] (https://github.com/ccuffs/poll-from-text/releases/tag/v.1.0.1) - 2021-06-23
### Added
- Static method `CCUFFS\Text\PollFromText::make()` for convinente parsing.
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,18 @@ array(1) {
}
```

### 3.1 Specific configuration

Both `parse()` and `make()` accept a `$config` which is an array of configuration to consider when generating the questionnaire. Below is a complete list of all available configuration options:

```php
$config = [
'multiline_question' => false, // if `true`, questions are allowed to have `\n` in their text.
'attr_validation' => PollFromText::ATTR_AS_TEXT, // allow any text a attribute (no validation)
];
```


### 4. Testing (related to the package development)

If you plan on changing how the package works, be sure to clone it first:
Expand Down
84 changes: 64 additions & 20 deletions src/PollFromText.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@
*/
class PollFromText
{
/**
* Allow question/answer attributes to be any text.
*/
public const ATTR_AS_TEXT = 1;

/**
* Force question/answer attributes to be a valid JSON string.
*/
public const ATTR_AS_STRICT_JSON = 2;

/**
* Lista of available attribute configurations.
*/
private array $attrConfigs = [
self::ATTR_AS_TEXT,
self::ATTR_AS_STRICT_JSON
];

/**
* @param mixed $text text to be parsed into a questionnaire
* @param array $config specific configuration for this parsing, e.g. allow multiline questions.
Expand All @@ -27,8 +45,8 @@ protected function split($text)
return preg_split('/\R+/', $text, 0, PREG_SPLIT_NO_EMPTY);
}

protected function findJsonStringStartingAt($startIndex, $text) {
$json = '';
protected function findDataStringStartingAt($startIndex, $text) {
$data = '';
$chars = 0;
$braces = 0;

Expand All @@ -38,21 +56,21 @@ protected function findJsonStringStartingAt($startIndex, $text) {

do {
$currentChar = $text[$startIndex];
$json .= $currentChar;
$data .= $currentChar;

if ($currentChar == '{') { $braces++; }
if ($currentChar == '}') { $braces--; }

if ($braces == 0) {
return $json;
return $data;
}

} while ($startIndex++ < strlen($text));

return $text;
}

protected function fillStructureWithDataAttribute(& $structure) {
protected function fillStructureWithDataAttribute(& $structure, array $config = []) {
$text = trim($structure['text']);
$textFirstChar = $text[0];

Expand All @@ -64,7 +82,7 @@ protected function fillStructureWithDataAttribute(& $structure) {
// {...} text here
// {...} text here { this should be text } again

$data = $this->findJsonStringStartingAt(0, $text);
$data = $this->findDataStringStartingAt(0, $text);

if (empty($data)) {
return false;
Expand All @@ -73,12 +91,12 @@ protected function fillStructureWithDataAttribute(& $structure) {
$textWithoutAttr = str_replace($data, '', $text);

$structure['text'] = trim($textWithoutAttr);
$structure['data'] = $this->decodeAttribute($textWithoutAttr, $data);
$structure['data'] = $this->decodeAttribute($textWithoutAttr, $data, $config);

return true;
}

protected function fillStructureWithOption(& $structure) {
protected function fillStructureWithOption(& $structure, array $config = []) {
$text = trim($structure['text']);
$firstChar = $text[0];
$optionChars = ['-', '*']; // TODO: make a config?
Expand Down Expand Up @@ -120,7 +138,7 @@ protected function fillStructureWithOption(& $structure) {
return true;
}

protected function extractStructure($text) {
protected function extractStructure($text, array $config = []) {
$text = trim($text);
$structure = [
'type' => 'question',
Expand All @@ -134,26 +152,47 @@ protected function extractStructure($text) {
// Something like the following:
// {...} text here
// {...} text here { this should be text } again
$this->fillStructureWithDataAttribute($structure);
$this->fillStructureWithDataAttribute($structure, $config);

// Something like the following:
// a) option text
// 1) option text
// no_space_here) option text
// - option text
// * option text
$this->fillStructureWithOption($structure);
$this->fillStructureWithOption($structure, $config);

return $structure;
}

protected function decodeAttribute($text, $dataAttr) {
try {
$flags = JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK;
$data = json_decode($dataAttr, true, 512, $flags);
return $data;
} catch (\JsonException $e) {
throw new \UnexpectedValueException("Data attribute '$dataAttr' is not valid JSON in use at text '$text'.", 1, $e);
protected function removeDataGuardChars($text) {
$text = trim($text);
return trim(substr($text, 1, -1));
}

protected function decodeAttribute($text, $dataAttr, array $config = []) {
$requestedAttrValidation = isset($config['attr_validation']) ? $config['attr_validation'] : self::ATTR_AS_TEXT;

foreach($this->attrConfigs as $availableAttrConfig) {
$requestedThisAttrValidation = ($requestedAttrValidation & $availableAttrConfig) != 0;

if (!$requestedThisAttrValidation) {
continue;
}

if ($availableAttrConfig == self::ATTR_AS_TEXT) {
return $this->removeDataGuardChars($dataAttr);

} else if ($availableAttrConfig == self::ATTR_AS_STRICT_JSON) {
try {
$flags = JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK;
$data = json_decode($dataAttr, true, 512, $flags);
return $data;

} catch (\JsonException $e) {
throw new \UnexpectedValueException("Data attribute '$dataAttr' is not valid JSON in use at text '$text'.", 1, $e);
}
}
}
}

Expand Down Expand Up @@ -209,7 +248,12 @@ protected function addOptionToPreviousQuestion(& $questions, array $structure) {
}

/**
* Parse the given text and return an array of questions.
*
* @param mixed $text text to be parsed into a questionnaire
* @param array $config specific configuration for this parsing, e.g. allow multiline questions.
*
* @return array associative array containing the structured questionnaire.
*/
public function parse($text, array $config = [])
{
Expand All @@ -233,11 +277,11 @@ public function parse($text, array $config = [])
continue;
}

$structure = $this->extractStructure($currentLine);
$structure = $this->extractStructure($currentLine, $config);
$currentType = $structure['type'];

if ($currentType == 'question') {
if($previousType == 'question' && @$config['mutliline_question']) {
if($previousType == 'question' && @$config['multiline_question']) {
$this->amendPreviousQuestion($questions, $structure);
} else {
$questions[] = $this->createQuestion($structure);
Expand Down
Loading

0 comments on commit e402484

Please sign in to comment.