Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Matias Rezende

[Resolvido] fscan f e acentos

Recommended Posts

Fala galera, tudo certo? Hoje sou eu precisando de uma ajuda aqui.

 

Estou montando uma aplicação que vai ler alguns arquivos e suas linhas. Abaixo parte do código:

 

public function processa() {
	while ( ! feof ( $this->_arquivo ) ) {
		list ( $tipoRegistro, $linha ) = fscanf ( $this->_arquivo, '%02d%248[ -~]' );
                       // o restante do código
	}

	fclose ( $this->_arquivo );
}

 

Um exemplo de linha que preciso ler:

02                                                                                                                        LUCIANE                                 1150                OUTROS SERVIÇOS/PACOTE RSNTI/Instalação de hardwar000000000000000

 

Preciso pegar primeiro os 2 primeiros caracteres do arquivo (%02d), que está OK. Depois, preciso pegar todo o resto (o que fazer com ele depende do valor dos 2 primeiros caracteres), mas quando tem qualquer caractere acentuado, o fscanf para de contar a linha daí em diante. Se eu der um var_dump em $linha:

 

string(192) "                                                                                                                        LUCIANE                                 1150                OUTROS SERVI"
// quando o esperado era
string(248) "                                                                                                                        LUCIANE                                 1150                OUTROS SERVIÇOS/PACOTE RSNTI/Instalação de hardwar000000000000000   "

 

Procurei no Oráculo e encontrei outras pessoas com o mesmo problema, mas nenhuma solução. Alguém já passou por isto e conseguiu resolver?

 

Todos os arquivos estão salvos em UTF-8.

 

Carlos Eduardo

Compartilhar este post


Link para o post
Compartilhar em outros sites

Matias,

 

Quando se trabalha com padrões de strings, você tem dois caminhos a seguir:

 

1. Especificação

2. Restrição

 

No fragmento abaixo, você está trabalhando com especificação:

 

fscanf ( $this->_arquivo, '%02d%248[ -~]' );

 

Que significa, "qualquer caractere imprimível com comprimento de 248 caracteres"

 

quando tem qualquer caractere acentuado, o fscanf para de contar a linha daí em diante. Se eu der um var_dump em $linha:

 

Todos os arquivos estão salvos em UTF-8.

 

Quando você se depara com um acento ou um cedilha, você encontrou o seguinte:

 

0xC3 0x87 => Ç

0xC3 0xA7 => ç

 

Bom, você disse para o fscanf que quer caracteres imprimíveis, mas o 0xC3 (que compõe o multibyte ç e outros caracteres latinos) não é imprimível e, como o fscanf é obediente ele pensou: "O cara não quer isso não, paro por aqui".

 

Qual a solução ?

 

Deixar de especificar e passar a restringir:

 

fscanf( $fh , "%02d%248[^\n]" )

 

Que significa: "qualquer caractere que não seja uma quebra de linha, com comprimento de 248 caracteres"

 

A saída:

 

array(2) {
 [0]=>
 int(2)
 [1]=>
 string(248) "                                                                                                                    	LUCIANE             					1150            	OUTROS SERVIÇOS/PACOTE RSNTI/Instalação de hardwar000000000000000"
}

 

;)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Uhn... pior que não funcionou. Tentei com \n e \r\n e nada, continua parando no acento. Provavelmente no servidor (Linux) vai funcionar, porque aqui no Windows não funcionou. Tem alguma outra sugestão do que posso colocar ali?

 

Carlos Eduardo

 

Falando contigo pelo MSN, você me sugeriu que eu utilizasse fgets ou fread. Só que depois desta separação, para cada $tipoRegistro encontrada eu preciso buscar um novo padrão dentro do restante ($linha). Para isto estou usando o sscanf, que apresenta o mesmo problema do fscanf.

 

Teria alguma outra sugestão para me ajudar neste caso?

 

Carlos Eduardo

Compartilhar este post


Link para o post
Compartilhar em outros sites

E se você combinasse o list() com um preg_split() ou fizesse uma preg_match_all() analisando manualmente? Não resolveria?

 

Pelo menos é sabido que ER não encrenca com acentos (pelo menos por enquanto).

Compartilhar este post


Link para o post
Compartilhar em outros sites

Falando contigo pelo MSN, você me sugeriu que eu utilizasse fgets ou fread. Só que depois desta separação, para cada $tipoRegistro encontrada eu preciso buscar um novo padrão dentro do restante ($linha). Para isto estou usando o sscanf, que apresenta o mesmo problema do fscanf.

 

Teria alguma outra sugestão para me ajudar neste caso?

 

Veja Matias,

 

Não sei exatamente quantos algorítimos você tem hoje mas, e se amanhã surgir um novo ?

 

Ou se algum desses algorítimos mudar ?

 

Se eu com esse mesmo problema, a primeira coisa que eu faria seria desacoplar os algorítimos, o desacoplamento causaria, de imediato, a facilidade de manutenção futura desses algorítimos e a possibilidade de escalá-los conforme as necessidades.

 

Você poderia simplesmente adicionar ou remover um algorítimo sem ter que modificar seu código.

 

Para resolver esse tipo de problema, existe um padrão bastante interessante chamado Strategy, que permite que você defina uma família de algorítimos, encapsule cada um deles e os torne intercambiáveis, ou seja, permite que você possa variá-los independentemente do cliente que os utiliza.

 

Strategy :seta: Comportamental

 

Conhecido também como:

Policy

Motivação:

Se você definir os algorítimos diretamente no cliente, você terá um código grande, complexo e difícil de manter.

 

Muitas vezes, algorítimos diferentes são necessários em situações diferentes, no seu caso, por exemplo, você tem uma forma de separação diferente para cada linha de um arquivo e essa separação é orientada pelos 2 primeiros bytes de cada linha.

 

Se você definir esses algorítimos no cliente, você terá um código não escalável, se um novo algorítimo surgir, você precisará editar seu cliente e adicionar o novo algorítimo, da mesma forma, caso algum algorítimo deixe de ser utilizado ou então precise ser modificado, você precisará editar constantemente o cliente que o utiliza.

Esses problemas podem ser resolvidos se você encapsular os algorítimos em objetos responsáveis exclusivamente pela sua lógica:

 

Aplicabilidade:

 

  • Classes relacionadas diferem apenas em seu comportamento. Strategy permite que você configure uma classe com um de vários comportamentos.
  • Você precisa ter variações de um algorítimo. Seu caso se enquadra bem aqui, existem situações que os dados de uma linha do arquivo é separado de uma forma totalmente diferente.
  • Um algorítimo utiliza dados que o cliente não deve conhecer. Com Strategy você consegue esconder dados complexos, específicos do algorítimo.
  • Se você tiver muitos algorítimos, você terá um bloco condicional muito grande para acomodar todos esses algorítimos, por exemplo:

switch ( $type ) {
case '01' :
case '02' :
case '03' :
//...
case '99' :
}

 

Com Strategy você utiliza apenas a interface do algorítimo, por exemplo:

 

$algorithm->execute();

 

Estrutura:

gallery_94216_34_6483.png

 

 

Participantes:

Strategy
:

Define a interface que é comum para todos os algorítimos. Context utiliza essa interface para executar o algorítimo definido por um ConcreteStrategy.

 

ConcreteStrategy
:

Implementa um algorítimo específico utilizando a interface definida por Strategy.

 

Context
:

É configurado com um objeto ConcreteStrategy.

Mantém uma referência ao objeto Strategy.

Pode definir uma interface que permite que o Strategy acesse seus dados.

 

Exemplo:

 

Bom, como estamos lidando com arquivos, o primeiro passo é remover a responsabilidade de trabalho com arquivos do cliente, para isso definiremos uma classe File, específica para esse fim:

 

File.php


<?php
/**
* Representação de um arquivo
*/
class File {
/**
* @var resource
*/
private $fh;

/**
* @var string
*/
private $filename;

/**
* @var string
*/
private $mode = 'r';

/**
* @var boolean
*/
private $opened = false;

/**
* Constroi o objeto que representa um arquivo.
* @param string $filename
* @throws RuntimeException
*/
public function __construct( $filename ) {
if ( is_file( $filename ) && is_readable( $filename ) ) {
if ( is_writable( $filename ) ) {
$this->mode = 'r+';
}
} else if ( is_writable( dirname( $filename ) ) ) {
$this->mode = 'w+';
} else {
throw new RuntimeException( 'Sem permissões para criar o arquivo.' );
}

$this->filename = $filename;
}

/**
* Destroi o objeto e fecha o arquivo se estiver aberto.
*/
public function __destruct() {
if ( is_resource( $this->fh ) ) {
$this->close();
}
}

/**
* Verifica se o ponteiro de arquivo está no início.
* @return boolean
*/
public function bof() {
if ( $this->opened ) {
return $this->tell() == 0;
}

return true;
}

/**
* Fecha o arquivo.
*/
public function close() {
if ( $this->opened ) {
fclose( $this->fh );
$this->opened = false;
}
}

/**
* Verifica se o ponteiro de arquivo está no final.
* @return boolean
*/
public function eof() {
if ( $this->opened ) {
return feof( $this->fh );
}

return true;
}

/**
* Recupera uma linha do arquivo.
* @param integer $length
* @return integer
*/
public function gets( $length = 1024 ) {
if ( $this->opened ) {
return fgets( $this->fh , $length );
}
}

/**
* Abre o arquivo.
* @return boolean
* @throws RuntimeException
*/
public function open() {
if ( !$this->opened ) {
$fh = fopen( $this->filename , $this->mode );

if ( is_resource( $fh ) ) {
$this->fh = $fh;
} else {
throw new RuntimeException( 'Não foi possível abrir o arquivo.' );
}

return $this->opened = true;
}

return false;
}

/**
* Lê uma porção de bytes do arquivo.
* @param integer $length
*/
public function read( $length = 1 ) {
if ( $this->opened ) {
return fread( $this->fh , $length );
}
}

/**
* Define a posição do ponteiro do arquivo.
* @param integer $offset
* @param integer $whence
*/
public function seek( $offset , $whence = SEEK_CUR ) {
if ( $this->opened ) {
return fseek( $this->fh , $offset , $whence ) == 0;
}

return false;
}

/**
* Recupera a posição do ponteiro do arquivo.
* @return integer
*/
public function tell() {
if ( $this->opened ) {
$tell = ftell( $this->fh );

return $tell === false ? -1 : $tell;
}

return -1;
}

/**
* Escreve no arquivo.
* @param string $data
* @return integer
*/
public function write( $data ) {
if ( $this->opened ) {
return fwrite( $this->fh , $data , strlen( $data ) );
}

return 0;
}
}

 

Com o objeto que representa um arquivo definido, vamos definir o Container:

 

Container.php


<?php
require_once 'File.php';

/**
* Implementação de um container que contém os dados a serem importados.
*/
class Container implements Countable {
/**
* @var array
*/
private $data = array();

/**
* @var File
*/
private $file;

/**
* @var Algorithm
*/
private $algorithm;

/**
* Constroi o objeto do container para um arquivo específico
* @param string $filename
*/
public function __construct( $filename ) {
$this->file = new File( $filename );
$this->file->open();
}

/**
* Adiciona uma linha que acaba de ser interpretada por um
* algorítimo qualquer.
* @param array $line
*/
public function addLine( $line ) {
$this->data[] = $line;
}

/**
* Configura o container com um algorítimo.
* @param Algorithm $algorithm
*/
public function configure( Algorithm $algorithm ) {
$this->algorithm = $algorithm;
$this->algorithm->accept( $this );
}

/**
* Conta o total de linhas já importadas.
* @return integer
* @see Countable::count()
*/
public function count() {
return count( $this->data );
}

/**
* Executa a importação utilizando um algorítimo previamente
* configurado.
*/
public function execute() {
$this->algorithm->execute();
}

/**
* Finaliza a importação.
*/
public function finalize() {
$this->file->close();
printf( "Finalizando\n" );

var_dump( $this->data );
}

/**
* Recupera uma linha inteira do arquivo.
* @return string
*/
public function gets() {
return $this->file->gets();
}

/**
* Lê uma porção de bytes do arquivo.
* @param integer $length
* @return string
*/
public function read( $length ) {
return $this->file->read( $length );
}

public function seek( $offset , $whence = SEEK_CUR ) {
$this->file->seek( $offset , $whence );
}

/**
* Verifica se o container ainda é válido (o arquivo já chegou no fim).
* @return boolean
*/
public function valid() {
return !$this->file->eof();
}
}

 

O Container é nosso Context, ele utilizará a interface do Strategy para executar a importação utilizando o algorítimo adequado.

 

Algorithm.php


<?php
/**
* Interface para definição de um algorítimo de importação
*/
interface Algorithm {
/**
* Define o contexto do algorítimo.
* @param Container $container
* @return boolean
*/
public function accept( Container $container );

/**
* Executa o algorítimo
*/
public function execute();
}

 

AbstractAlgorithm.php


<?php
require_once 'Algorithm.php';

/**
* Base para implementação do algorítimo de importação.
*/
abstract class AbstractAlgorithm implements Algorithm {
/**
* @var boolean
*/
protected $acceptable = false;

/**
* @var Container
*/
protected $container;

/**
* @var string
*/
protected $type;

/**
* @param Container $container
* @return boolean
*/
public function accept( Container $container ) {
if ( $container->read( 2 ) != $this->type ) {
$container->seek( -2 );

return $this->acceptable = false;
}

$this->container = $container;

return $this->acceptable = true;
}
}

 

Com a interface de um algorítimo definida, partimos para as implementações de cada algorítimo específico:

 

Algorithm01.php


<?php
require_once 'AbstractAlgorithm.php';

class Algorithm01 extends AbstractAlgorithm {
/**
* @var string
*/
protected $type = '01';

/**
* @see Algorithm::execute()
*/
public function execute() {
if ( $this->acceptable ) {
$line = array();

$this->container->seek( 4 );

$line[ 'oco_dataHoraAbertura' ] = vsprintf(
'%4d-%02d-%02d %02d:%02d:%02d' , sscanf(
$this->container->read( 14 ),
'%04d%02d%02d%02d%02d%02d'
)
);

$line[ 'oco_numeroChamadoCaixa' ] = trim( $this->container->read( 20 ) );
$line[ 'oco_codigoChamadoCaixa' ] = $line[ 'oco_numeroChamadoCaixa' ];
$line[ 'oco_codigoAtividade' ] = $this->container->read( 3 );

$this->container->seek( 3 );

$line[ 'oco_descricao' ] = vsprintf(
'UNIDADE DA OCORRÊNCIA => Código Unidade - %d, Tipo Unidade - %d, Nome - %s, Endereço - %s, %s - %s, Correspondente - ',
array_map( 'trim' , array(
$this->container->read( 10 ),
$this->container->read( 30 ),
$this->container->read( 40 ),
$this->container->read( 60 ),
$this->container->read( 40 ),
$this->container->read( 2 ),
$this->container->read( 10 )
) )
);

$this->container->addLine( $line );
$this->container->gets();
}
}
}

 

Algorithm02.php


<?php
require_once 'AbstractAlgorithm.php';

class Algorithm02 extends AbstractAlgorithm {
/**
* @var string
*/
protected $type = '02';

/**
* @see Algorithm::execute()
*/
public function execute() {
if ( $this->acceptable ) {
$this->container->addLine( array(
'oco_descricao' => implode( ',' , array(
'EQUIPAMENTO DA OCORRÊNCIA => ' . trim( $this->container->read( 40 ) ),
'Número de Série - ' . trim( $this->container->read( 40 ) ),
'Nome lógico na rede - ' . trim( $this->container->read( 40 ) ),
'Contato - ' . trim( $this->container->read( 40 ) ),
'Telefone - ' . trim( $this->container->read( 20 ) ),
'Tipo de Serviço para a Caixa - ' . trim( $this->container->read( 50 ) )
) ) ) );

$this->container->gets();
}
}
}

 

Algorithm03.php


<?php
require_once 'AbstractAlgorithm.php';

class Algorithm03 extends AbstractAlgorithm {
/**
* @var string
*/
protected $type = '03';

/**
* @see Algorithm::execute()
*/
public function execute() {
if ( $this->acceptable ) {
$this->container->addLine( array(
'oco_descricao' => $this->container->gets()
) );
}
}
}

 

Algorithm99.php


<?php
require_once 'AbstractAlgorithm.php';

class Algorithm99 extends AbstractAlgorithm {
/**
* @var string
*/
protected $type = '99';

/**
* @see Algorithm::execute()
*/
public function execute() {
if ( $this->acceptable ) {
if ( $this->container->count() + 1 != (int) $this->container->read( 5 ) ) {
throw new RuntimeException( 'A quantidade de linhas no arquivo não bate com o existente no trailer' );
}

$this->container->finalize();
}
}
}

 

Com os algorítimos encapsulados, preciamos apenas definir o Client:

 

Importer.php


<?php
require_once 'Container.php';
require_once 'Algorithm01.php';
require_once 'Algorithm02.php';
require_once 'Algorithm03.php';
require_once 'Algorithm99.php';

/**
* Implementação de um importador que aceita vários
* algorítimos de importação.
*/
class Importer {
/**
* @var array[Algorithm]
*/
private $algorithms;

/**
* Adiciona um algorítimo de importação.
* @param Algorithm $algorithm
*/
public function addAlgorithm( Algorithm $algorithm ) {
$this->algorithms[] = $algorithm;
}

/**
* Importa os dados de um determinado container.
* @param Container $container
* @throws BadMethodCallException
*/
public function import( Container $container ) {
if ( count( $this->algorithms ) > 0 ) {
while ( $container->valid() ) {
foreach ( $this->algorithms as $algorithm ) {
$container->configure( $algorithm );
$container->execute();
}
}
} else {
throw new BadMethodCallException( 'Nenhum algorítimo de importação definido.' );
}
}
}

 

Usando isso ai:


<?php
require_once 'Importer.php';

$i = new Importer();
$i->addAlgorithm( new Algorithm01() );
$i->addAlgorithm( new Algorithm02() );
$i->addAlgorithm( new Algorithm03() );
$i->addAlgorithm( new Algorithm99() );
$i->import( new Container( '00000000000053217462.txt' ) );

 

E pronto, se amanhã algum algorítimo mudar, basta ajustar o próprio algorítimo.

 

Se você precisar adicionar mais algorítimos, remover alguns, utilizar algorítimos específicos para arquivos específicos, enfim...

 

;)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Show de bola!!!! Segunda feira vou analisar com calma e implementar. Se surgir alguma dúvida eu volto a perguntar.

 

Como sempre, João, ótimo exemplo.

 

Carlos Eduardo

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.