diff --git a/src/Controller/ApiLabelController.php b/src/Controller/ApiLabelController.php index 6c628c50d9fd812daad813ea982ae0cd5daecc12..af573a85c15fb184e7ff64bc6c9ad3768a5ba135 100644 --- a/src/Controller/ApiLabelController.php +++ b/src/Controller/ApiLabelController.php @@ -4,9 +4,11 @@ namespace App\Controller; use App\Entity\Entry; use App\Entity\Graphy; +use App\Entity\Group; use App\Entity\Headword; use App\Entity\Label; use App\Entity\Lexicon; +use App\Entity\User; use App\Manager\WiktionaryManager; use App\Repository\LabelRepository; use Doctrine\Persistence\ManagerRegistry; @@ -71,9 +73,11 @@ class ApiLabelController extends ApiBaseController * @OA\RequestBody( * required=true, * @OA\JsonContent( - * required={"name", "category"}, + * required={"name", "category", "masters_type"}, * @OA\Property(property="name", type="string"), - * @OA\Property(property="category", type="string", example="morphological OR thematic OR list OR milestone"), + * @OA\Property(property="category", type="string", example="morphological OR general OR list OR milestone OR institutional"), + * @OA\Property(property="masters_type", type="string", example="user OR group OR public"), + * @OA\Property(property="master_id", type="integer"), * @OA\Property(property="milestone", type="date") * ) * ) @@ -86,12 +90,16 @@ class ApiLabelController extends ApiBaseController if (!$data) { return $this->createJsonResponse(401, ['error' => sprintf("Json non valide")]); } - if ($missingFields = $this->getMissingFields($data, ['name', 'category'])) { + if ($missingFields = $this->getMissingFields($data, ['name', 'category', 'masters_type'])) { return $this->createJsonResponse(401, ['error' => sprintf("Veuillez fournir une valeur pour: %s", implode(', ', $missingFields))]); } if (!in_array($data['category'], Label::LABEL_LIST_CATEGORIES)) { return $this->createJsonResponse(401, ['error' => sprintf("La catégorie de label %s n'existe pas", $data['category'])]); } + if (!in_array($data['masters_type'], Label::MASTERS_TYPES)) { + return $this->createJsonResponse(401, ['error' => sprintf("Le type de masters de label %s n'existe pas", $data['masters_type'])]); + } + $milestone = $data['milestone'] ?? null; if ($data['category'] === Label::LABEL_CATEGORY_MILESTONE && (!$milestone || !date_create_from_format('Y-m-d', $milestone))) { @@ -99,7 +107,23 @@ class ApiLabelController extends ApiBaseController } $label = new Label($data['category']); - $label->setUser($this->getUser()); + + $mastersType = $data['mastersType']; + $masterId = $data['master_id'] ?? null; + if ($mastersType !== Label::MASTER_PUBLIC && !$masterId) { + return $this->createJsonResponse(401, ['error' => sprintf("Veuillez fournir une valeur pour « master_id »")]); + } + if ($data['masters_type'] === Label::MASTER_GROUP) { + $group = $this->doctrine->getRepository(Group::class)->find($masterId); + if (!$group) return $this->createJsonResponse(401, ['error' => sprintf("Impossible de trouver le groupe pour l'id «%s»", $masterId)]); + $label->setGroup($group); + } + if ($data['masters_type'] === Label::MASTER_PERSONAL) { + $user = $this->doctrine->getRepository(User::class)->find($masterId); + if (!$user) return $this->createJsonResponse(401, ['error' => sprintf("Impossible de trouver l'utilisateur pour l'id «%s»", $masterId)]); + $label->setUser($user); + } + $label->setName($data['name']); if ($label->isMilestone()) { $label->setMilestone(date_create_from_format('Y-m-d', $milestone)); diff --git a/src/Entity/Headword.php b/src/Entity/Headword.php index 0a901efec6da879c4863bbca8e6d6a77d5a438b1..022fde791d9e053ae75c51a3e7e0b48a2c85109e 100644 --- a/src/Entity/Headword.php +++ b/src/Entity/Headword.php @@ -72,6 +72,20 @@ class Headword $this->labels = new ArrayCollection(); } + /** + * @return Label[] + */ + public function getInstitutionalLabels() + { + $result = []; + foreach ($this->getLabels() as $label) { + if ($label->isInstitutional()) { + $result[] = $label; + } + } + return $result; + } + /** * @return Label[] */ @@ -89,11 +103,11 @@ class Headword /** * @return Label[] */ - public function getThematicLabels() + public function getGeneralLabels() { $result = []; foreach ($this->getLabels() as $label) { - if ($label->isThematic()) { + if ($label->isGeneral()) { $result[] = $label; } } diff --git a/src/Entity/Label.php b/src/Entity/Label.php index d05111f5b0df6c778e378338264c82be75fd56e3..1a91d00fb6c745a3425a57186b3f38aa89e2f02a 100644 --- a/src/Entity/Label.php +++ b/src/Entity/Label.php @@ -7,6 +7,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity(repositoryClass=LabelRepository::class) @@ -14,19 +15,31 @@ use Symfony\Component\Serializer\Annotation\Groups; */ class Label { + const LABEL_CATEGORY_INSTITUTIONAL = 'institutional'; const LABEL_CATEGORY_MORPHOLOGICAL = 'morphological'; - const LABEL_CATEGORY_THEMATIC = 'thematic'; - const LABEL_CATEGORY_LIST = 'list'; const LABEL_CATEGORY_MILESTONE = 'milestone'; + const LABEL_CATEGORY_GENERAL = 'general'; + const LABEL_CATEGORY_LIST = 'list'; const LABEL_CATEGORY_SYSTEM = 'system'; const LABEL_LIST_CATEGORIES = [ + self::LABEL_CATEGORY_INSTITUTIONAL => self::LABEL_CATEGORY_INSTITUTIONAL, self::LABEL_CATEGORY_MORPHOLOGICAL => self::LABEL_CATEGORY_MORPHOLOGICAL, - self::LABEL_CATEGORY_THEMATIC => self::LABEL_CATEGORY_THEMATIC, + self::LABEL_CATEGORY_GENERAL => self::LABEL_CATEGORY_GENERAL, self::LABEL_CATEGORY_LIST => self::LABEL_CATEGORY_LIST, self::LABEL_CATEGORY_MILESTONE => self::LABEL_CATEGORY_MILESTONE, ]; + const MASTER_PERSONAL = 'personal'; + const MASTER_GROUP = 'group'; + const MASTER_PUBLIC = 'public'; + + const MASTERS_TYPES = [ + self::MASTER_GROUP => self::MASTER_GROUP, + self::MASTER_PERSONAL => self::MASTER_PERSONAL, + self::MASTER_PUBLIC => self::MASTER_PUBLIC, + ]; + const LABEL_MERGED = 'merged'; /** @@ -48,6 +61,13 @@ class Label */ private $category; + /** + * @ORM\Column(type="string", length=255) + * @Groups({"label:read"}) + * @Assert\Length(max=255) + */ + private $source; + /** * @ORM\Column(type="datetime", nullable=true) * @Groups({"label:read"}) @@ -87,10 +107,22 @@ class Label */ private $graphyList; + /** + * @ORM\OneToMany(targetEntity=UpdateRequest::class, mappedBy="label") + */ + private $updateRequests; + + /** + * @ORM\OneToMany(targetEntity=LabelVisibility::class, mappedBy="label") + */ + private $labelVisibilities; + public function __construct($category) { $this->setCategory($category); $this->headwords = new ArrayCollection(); + $this->updateRequests = new ArrayCollection(); + $this->labelVisibilities = new ArrayCollection(); } public function __toString() @@ -98,6 +130,15 @@ class Label return $this->getName(); } + public function getMasters() + { + if ($this->getUser() && ! $this->getGroup()) return self::MASTER_PERSONAL; + if ($this->getGroup() && $this->getUser()) return self::MASTER_GROUP; + if (! $this->getGroup() && ! $this->getUser()) return self::MASTER_PUBLIC; + + throw new \LogicException("Cannot determine label masters type"); + } + public function compareWith(Label $label) { $diffs = []; @@ -123,13 +164,17 @@ class Label return $diffs; } + public function isInstitutional() + { + return $this->getCategory() === self::LABEL_CATEGORY_INSTITUTIONAL; + } public function isMorphological() { return $this->getCategory() === self::LABEL_CATEGORY_MORPHOLOGICAL; } - public function isThematic() + public function isGeneral() { - return $this->getCategory() === self::LABEL_CATEGORY_THEMATIC; + return $this->getCategory() === self::LABEL_CATEGORY_GENERAL; } public function isList() { @@ -280,4 +325,76 @@ class Label return $this; } + + public function getSource(): ?string + { + return $this->source; + } + + public function setSource(string $source): self + { + $this->source = $source; + + return $this; + } + + /** + * @return Collection<int, UpdateRequest> + */ + public function getUpdateRequests(): Collection + { + return $this->updateRequests; + } + + public function addUpdateRequest(UpdateRequest $updateRequest): self + { + if (!$this->updateRequests->contains($updateRequest)) { + $this->updateRequests[] = $updateRequest; + $updateRequest->setLabel($this); + } + + return $this; + } + + public function removeUpdateRequest(UpdateRequest $updateRequest): self + { + if ($this->updateRequests->removeElement($updateRequest)) { + // set the owning side to null (unless already changed) + if ($updateRequest->getLabel() === $this) { + $updateRequest->setLabel(null); + } + } + + return $this; + } + + /** + * @return Collection<int, LabelVisibility> + */ + public function getLabelVisibilities(): Collection + { + return $this->labelVisibilities; + } + + public function addLabelVisibility(LabelVisibility $labelVisibility): self + { + if (!$this->labelVisibilities->contains($labelVisibility)) { + $this->labelVisibilities[] = $labelVisibility; + $labelVisibility->setLabel($this); + } + + return $this; + } + + public function removeLabelVisibility(LabelVisibility $labelVisibility): self + { + if ($this->labelVisibilities->removeElement($labelVisibility)) { + // set the owning side to null (unless already changed) + if ($labelVisibility->getLabel() === $this) { + $labelVisibility->setLabel(null); + } + } + + return $this; + } } diff --git a/src/Entity/LabelVisibility.php b/src/Entity/LabelVisibility.php new file mode 100644 index 0000000000000000000000000000000000000000..c0dfad8743f64b956e9d20b156947a8b173e00f0 --- /dev/null +++ b/src/Entity/LabelVisibility.php @@ -0,0 +1,118 @@ +<?php + +namespace App\Entity; + +use App\Repository\LabelRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * @ORM\Entity(repositoryClass=VoteRepository::class) + * @ORM\HasLifecycleCallbacks + */ +class LabelVisibility +{ + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="datetime_immutable") + */ + private $createdAt; + + /** + * @ORM\Column(type="datetime_immutable", nullable=true) + */ + private $updatedAt; + + /** + * @ORM\ManyToOne(targetEntity=User::class, inversedBy="labelVisibilities") + * @Groups({"labelVisibility:read"}) + */ + private $user; + + /** + * @ORM\ManyToOne(targetEntity=Label::class, inversedBy="labelVisibilities") + * @Groups({"labelVisibility:read"}) + */ + private $label; + + /** + * @ORM\ManyToOne(targetEntity=Lexicon::class, inversedBy="labelVisibilities") + * @Groups({"labelVisibility:read"}) + */ + private $lexicon; + + public function getId(): ?int + { + return $this->id; + } + + public function getCreatedAt(): ?\DateTimeImmutable + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeImmutable $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function setUpdatedAt(?\DateTimeImmutable $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): self + { + $this->user = $user; + + return $this; + } + + public function getLabel(): ?Label + { + return $this->label; + } + + public function setLabel(?Label $label): self + { + $this->label = $label; + + return $this; + } + + public function getLexicon(): ?Lexicon + { + return $this->lexicon; + } + + public function setLexicon(?Lexicon $lexicon): self + { + $this->lexicon = $lexicon; + + return $this; + } + +} diff --git a/src/Entity/LabelVisibilityRepository.php b/src/Entity/LabelVisibilityRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..2ef59849072581391ff84f62234e8a14b04672a9 --- /dev/null +++ b/src/Entity/LabelVisibilityRepository.php @@ -0,0 +1,66 @@ +<?php + +namespace App\Entity; + +use App\Entity\LabelVisibility; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<LabelVisibility> + * + * @method LabelVisibility|null find($id, $lockMode = null, $lockVersion = null) + * @method LabelVisibility|null findOneBy(array $criteria, array $orderBy = null) + * @method LabelVisibility[] findAll() + * @method LabelVisibility[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class LabelVisibilityRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, LabelVisibility::class); + } + + public function add(LabelVisibility $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(LabelVisibility $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + +// /** +// * @return LabelVisibility[] Returns an array of LabelVisibility objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('v') +// ->andWhere('v.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('v.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?LabelVisibility +// { +// return $this->createQueryBuilder('v') +// ->andWhere('v.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Entity/Lexicon.php b/src/Entity/Lexicon.php index ab29022759cf715ba54b185cd979ac48ed576677..0625fd2b3c84aa754b1bcf86cba005c933f47a44 100644 --- a/src/Entity/Lexicon.php +++ b/src/Entity/Lexicon.php @@ -106,6 +106,11 @@ class Lexicon */ private $logs; + /** + * @ORM\OneToMany(targetEntity=LabelVisibility::class, mappedBy="lexicon") + */ + private $labelVisibilities; + public function __toString() { switch ($this->getCategory()) { @@ -121,6 +126,7 @@ class Lexicon $this->entries = new ArrayCollection(); $this->graphyLists = new ArrayCollection(); $this->logs = new ArrayCollection(); + $this->labelVisibilities = new ArrayCollection(); } public function isZero() @@ -341,4 +347,34 @@ class Lexicon return $this; } + + /** + * @return Collection<int, LabelVisibility> + */ + public function getLabelVisibilities(): Collection + { + return $this->labelVisibilities; + } + + public function addLabelVisibility(LabelVisibility $labelVisibility): self + { + if (!$this->labelVisibilities->contains($labelVisibility)) { + $this->labelVisibilities[] = $labelVisibility; + $labelVisibility->setLexicon($this); + } + + return $this; + } + + public function removeLabelVisibility(LabelVisibility $labelVisibility): self + { + if ($this->labelVisibilities->removeElement($labelVisibility)) { + // set the owning side to null (unless already changed) + if ($labelVisibility->getLexicon() === $this) { + $labelVisibility->setLexicon(null); + } + } + + return $this; + } } diff --git a/src/Entity/UpdateRequest.php b/src/Entity/UpdateRequest.php new file mode 100644 index 0000000000000000000000000000000000000000..b8ad52b9cff220af493e25bcb932c6af5d467536 --- /dev/null +++ b/src/Entity/UpdateRequest.php @@ -0,0 +1,190 @@ +<?php + +namespace App\Entity; + +use App\Repository\LabelRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * @ORM\Entity(repositoryClass=UpdateRequestRepository::class) + * @ORM\HasLifecycleCallbacks + */ +class UpdateRequest +{ + const CATEGORY_RENAME = 'rename'; + const CATEGORY_DELETE = 'delete'; + + const CATEGORY_LIST = [ + self::CATEGORY_RENAME => self::CATEGORY_RENAME, + self::CATEGORY_DELETE => self::CATEGORY_DELETE, + ]; + + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="string", length=255) + * @Groups({"updateRequest:read"}) + */ + private $suggestedName; + + /** + * @ORM\Column(type="string", length=80) + * @Groups({"updateRequest:read"}) + */ + private $category; + + /** + * @ORM\ManyToOne(targetEntity=Label::class, inversedBy="updateRequests") + */ + private $label; + + /** + * @ORM\Column(type="datetime_immutable") + */ + private $createdAt; + + /** + * @ORM\Column(type="datetime_immutable", nullable=true) + */ + private $updatedAt; + + /** + * @ORM\ManyToOne(targetEntity=User::class, inversedBy="updateRequests") + * @Groups({"updateRequest:read"}) + */ + private $user; + + /** + * @ORM\OneToMany(targetEntity=Vote::class, mappedBy="updateRequest") + * @Groups({"updateRequest:read"}) + */ + private $votes; + + public function __construct($category) + { + $this->setCategory($category); + $this->votes = new ArrayCollection(); + } + + public function __toString() + { + return sprintf("update request du %s label «%s» par %s", $this->getCreatedAt()->format('d/m/Y'), $this->getLabel(), $this->getUser()); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getSuggestedName(): ?string + { + return $this->suggestedName; + } + + public function setSuggestedName(string $suggestedName): self + { + $this->suggestedName = $suggestedName; + + return $this; + } + + public function getCategory(): ?string + { + return $this->category; + } + + public function setCategory(string $category): self + { + $this->category = $category; + + return $this; + } + + public function getCreatedAt(): ?\DateTimeImmutable + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeImmutable $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function setUpdatedAt(?\DateTimeImmutable $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; + } + + public function getLabel(): ?Label + { + return $this->label; + } + + public function setLabel(?Label $label): self + { + $this->label = $label; + + return $this; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): self + { + $this->user = $user; + + return $this; + } + + /** + * @return Collection<int, Vote> + */ + public function getVotes(): Collection + { + return $this->votes; + } + + public function addVote(Vote $vote): self + { + if (!$this->votes->contains($vote)) { + $this->votes[] = $vote; + $vote->setUpdateRequest($this); + } + + return $this; + } + + public function removeVote(Vote $vote): self + { + if ($this->votes->removeElement($vote)) { + // set the owning side to null (unless already changed) + if ($vote->getUpdateRequest() === $this) { + $vote->setUpdateRequest(null); + } + } + + return $this; + } + +} diff --git a/src/Entity/UpdateRequestRepository.php b/src/Entity/UpdateRequestRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..c7b5182443791adf4be20126326796740391f25d --- /dev/null +++ b/src/Entity/UpdateRequestRepository.php @@ -0,0 +1,66 @@ +<?php + +namespace App\Entity; + +use App\Entity\UpdateRequest; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<UpdateRequest> + * + * @method UpdateRequest|null find($id, $lockMode = null, $lockVersion = null) + * @method UpdateRequest|null findOneBy(array $criteria, array $orderBy = null) + * @method UpdateRequest[] findAll() + * @method UpdateRequest[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class UpdateRequestRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, UpdateRequest::class); + } + + public function add(UpdateRequest $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(UpdateRequest $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + +// /** +// * @return UpdateRequest[] Returns an array of UpdateRequest objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('u.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?UpdateRequest +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 3d844f9c6889a577b10bd9b177e3083bf9f80c77..66f3d0ab72aea157180fb7ada9164505c17a71cc 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -201,6 +201,21 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface */ private $metaInfos; + /** + * @ORM\OneToMany(targetEntity=UpdateRequest::class, mappedBy="user") + */ + private $updateRequests; + + /** + * @ORM\OneToMany(targetEntity=Vote::class, mappedBy="user") + */ + private $votes; + + /** + * @ORM\OneToMany(targetEntity=LabelVisibility::class, mappedBy="user") + */ + private $labelVisibilities; + public function __toString() { return $this->getPseudo(); @@ -222,6 +237,11 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface $this->labels = new ArrayCollection(); $this->createdGraphyLists = new ArrayCollection(); $this->logs = new ArrayCollection(); + $this->traces = new ArrayCollection(); + $this->metaInfos = new ArrayCollection(); + $this->updateRequests = new ArrayCollection(); + $this->votes = new ArrayCollection(); + $this->labelVisibilities = new ArrayCollection(); } /** @@ -808,4 +828,154 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + + /** + * @return Collection<int, Trace> + */ + public function getTraces(): Collection + { + return $this->traces; + } + + public function addTrace(Trace $trace): self + { + if (!$this->traces->contains($trace)) { + $this->traces[] = $trace; + $trace->setCreatedBy($this); + } + + return $this; + } + + public function removeTrace(Trace $trace): self + { + if ($this->traces->removeElement($trace)) { + // set the owning side to null (unless already changed) + if ($trace->getCreatedBy() === $this) { + $trace->setCreatedBy(null); + } + } + + return $this; + } + + /** + * @return Collection<int, MetaInfo> + */ + public function getMetaInfos(): Collection + { + return $this->metaInfos; + } + + public function addMetaInfo(MetaInfo $metaInfo): self + { + if (!$this->metaInfos->contains($metaInfo)) { + $this->metaInfos[] = $metaInfo; + $metaInfo->setCreatedBy($this); + } + + return $this; + } + + public function removeMetaInfo(MetaInfo $metaInfo): self + { + if ($this->metaInfos->removeElement($metaInfo)) { + // set the owning side to null (unless already changed) + if ($metaInfo->getCreatedBy() === $this) { + $metaInfo->setCreatedBy(null); + } + } + + return $this; + } + + /** + * @return Collection<int, UpdateRequest> + */ + public function getUpdateRequests(): Collection + { + return $this->updateRequests; + } + + public function addUpdateRequest(UpdateRequest $updateRequest): self + { + if (!$this->updateRequests->contains($updateRequest)) { + $this->updateRequests[] = $updateRequest; + $updateRequest->setUser($this); + } + + return $this; + } + + public function removeUpdateRequest(UpdateRequest $updateRequest): self + { + if ($this->updateRequests->removeElement($updateRequest)) { + // set the owning side to null (unless already changed) + if ($updateRequest->getUser() === $this) { + $updateRequest->setUser(null); + } + } + + return $this; + } + + /** + * @return Collection<int, Vote> + */ + public function getVotes(): Collection + { + return $this->votes; + } + + public function addVote(Vote $vote): self + { + if (!$this->votes->contains($vote)) { + $this->votes[] = $vote; + $vote->setUser($this); + } + + return $this; + } + + public function removeVote(Vote $vote): self + { + if ($this->votes->removeElement($vote)) { + // set the owning side to null (unless already changed) + if ($vote->getUser() === $this) { + $vote->setUser(null); + } + } + + return $this; + } + + /** + * @return Collection<int, LabelVisibility> + */ + public function getLabelVisibilities(): Collection + { + return $this->labelVisibilities; + } + + public function addLabelVisibility(LabelVisibility $labelVisibility): self + { + if (!$this->labelVisibilities->contains($labelVisibility)) { + $this->labelVisibilities[] = $labelVisibility; + $labelVisibility->setUser($this); + } + + return $this; + } + + public function removeLabelVisibility(LabelVisibility $labelVisibility): self + { + if ($this->labelVisibilities->removeElement($labelVisibility)) { + // set the owning side to null (unless already changed) + if ($labelVisibility->getUser() === $this) { + $labelVisibility->setUser(null); + } + } + + return $this; + } } diff --git a/src/Entity/Vote.php b/src/Entity/Vote.php new file mode 100644 index 0000000000000000000000000000000000000000..11b944edc6a70f748cc7070b8384c40b6fb64a14 --- /dev/null +++ b/src/Entity/Vote.php @@ -0,0 +1,141 @@ +<?php + +namespace App\Entity; + +use App\Repository\LabelRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * @ORM\Entity(repositoryClass=VoteRepository::class) + * @ORM\HasLifecycleCallbacks + */ +class Vote +{ + const CATEGORY_YES = 'yes'; + const CATEGORY_NO = 'no'; + + const CATEGORY_LIST = [ + self::CATEGORY_YES => self::CATEGORY_YES, + self::CATEGORY_NO => self::CATEGORY_NO, + ]; + + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="string", length=80) + * @Groups({"vote:read"}) + */ + private $category; + + /** + * @ORM\Column(type="datetime_immutable") + */ + private $createdAt; + + /** + * @ORM\Column(type="datetime_immutable", nullable=true) + */ + private $updatedAt; + + /** + * @ORM\ManyToOne(targetEntity=User::class, inversedBy="votes") + * @Groups({"vote:read"}) + */ + private $user; + + /** + * @ORM\ManyToOne(targetEntity=UpdateRequest::class, inversedBy="votes") + * @Groups({"vote:read"}) + */ + private $updateRequest; + + public function __construct($category) + { + $this->setCategory($category); + } + + public function __toString() + { + return sprintf("vote %s du %s pour label «%s» par %s", $this->getCategory(), $this->getCreatedAt()->format('d/m/Y'), $this->getLabel(), $this->getUser()); + } + + public function getLabel() + { + return $this->getUpdateRequest()->getLabel(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getCategory(): ?string + { + return $this->category; + } + + public function setCategory(string $category): self + { + $this->category = $category; + + return $this; + } + + public function getCreatedAt(): ?\DateTimeImmutable + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeImmutable $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function setUpdatedAt(?\DateTimeImmutable $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): self + { + $this->user = $user; + + return $this; + } + + public function getUpdateRequest(): ?UpdateRequest + { + return $this->updateRequest; + } + + public function setUpdateRequest(?UpdateRequest $updateRequest): self + { + $this->updateRequest = $updateRequest; + + return $this; + } + +} diff --git a/src/Entity/VoteRepository.php b/src/Entity/VoteRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..dbcd977c496c5b1176d5435b1e7157cba33e013f --- /dev/null +++ b/src/Entity/VoteRepository.php @@ -0,0 +1,66 @@ +<?php + +namespace App\Entity; + +use App\Entity\Vote; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Vote> + * + * @method Vote|null find($id, $lockMode = null, $lockVersion = null) + * @method Vote|null findOneBy(array $criteria, array $orderBy = null) + * @method Vote[] findAll() + * @method Vote[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class VoteRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Vote::class); + } + + public function add(Vote $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(Vote $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + +// /** +// * @return Vote[] Returns an array of Vote objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('v') +// ->andWhere('v.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('v.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Vote +// { +// return $this->createQueryBuilder('v') +// ->andWhere('v.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Repository/UpdateRequestRepository.php b/src/Repository/UpdateRequestRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..8c819c1b143f6b1a0c556dc90f1d69515398537b --- /dev/null +++ b/src/Repository/UpdateRequestRepository.php @@ -0,0 +1,66 @@ +<?php + +namespace App\Repository; + +use App\Entity\UpdateRequest; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<UpdateRequest> + * + * @method UpdateRequest|null find($id, $lockMode = null, $lockVersion = null) + * @method UpdateRequest|null findOneBy(array $criteria, array $orderBy = null) + * @method UpdateRequest[] findAll() + * @method UpdateRequest[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class UpdateRequestRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, UpdateRequest::class); + } + + public function add(UpdateRequest $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(UpdateRequest $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + +// /** +// * @return UpdateRequest[] Returns an array of UpdateRequest objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('l') +// ->andWhere('l.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('l.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?UpdateRequest +// { +// return $this->createQueryBuilder('l') +// ->andWhere('l.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Repository/VoteRepository.php b/src/Repository/VoteRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..b1a9518ba7472dd9b52fa1285bcdfe9d7ae2a410 --- /dev/null +++ b/src/Repository/VoteRepository.php @@ -0,0 +1,66 @@ +<?php + +namespace App\Repository; + +use App\Entity\Vote; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; + +/** + * @extends ServiceEntityRepository<Vote> + * + * @method Vote|null find($id, $lockMode = null, $lockVersion = null) + * @method Vote|null findOneBy(array $criteria, array $orderBy = null) + * @method Vote[] findAll() + * @method Vote[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class VoteRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Vote::class); + } + + public function add(Vote $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(Vote $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + +// /** +// * @return Vote[] Returns an array of Vote objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('l') +// ->andWhere('l.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('l.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Vote +// { +// return $this->createQueryBuilder('l') +// ->andWhere('l.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Wikstraktor/wikstraktor_new.py b/src/Wikstraktor/wikstraktor_new.py new file mode 100644 index 0000000000000000000000000000000000000000..fec1efb131518ec026be156fa3cdd4f42538cf62 --- /dev/null +++ b/src/Wikstraktor/wikstraktor_new.py @@ -0,0 +1,668 @@ +#!/usr/bin/env python3 +import pywikibot +import wikitextparser +import importlib +import json +from wikstraktor_version import version as the_version +from wikstraklog import Wikstraklog + +#ICITE : fr marche pas, en prend des trucs vides à virer (cf. yellow… def & example) + + +class SubInfo: + next_id = 1 + prfx = "err" + + @classmethod + def inc_n_id(cls): + cls.next_id += 1 + + @classmethod + def reset(cls): + cls.next_id = 0 + + def __init__(self, prefix = None): + self.id = None + self.set_id(prefix) + + def set_id(self, prefix): + if self.id == None and prefix != None: + self.id = f"{prefix}_{self.__class__.prfx}{self.__class__.next_id}" + self.__class__.inc_n_id() + return self.id + + def serializable(self, prefix = None): + res = {} + if self.set_id(prefix) != None: + res["id"] = self.id + return res + + + +####### +# Oral +####### +class Sound: + def __init__(self, url, accent): + self.url = url + self.accent = accent + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.url == other.url and self.accent == other.accent + + def serializable(self): + if self.accent == None: + res = {"url":self.url} + else: + res = {"accent":self.accent, "url":self.url} + return res + +class Pronunciation(SubInfo): + prfx = "prn" + + def __init__(self, prefix = None): + super().__init__(prefix) + self.ipa = None + self.sounds = [] + self.accent = None + + def set_transcription(self, tscpt): + self.ipa = tscpt + + def set_accent(self, accent): + self.accent = accent + + def add_sound(self, url, accent=None): + self.sounds.append(Sound(url,accent)) + + def serializable(self, prefix = None): + snds = [] + for s in self.sounds: + snds.append(s.serializable()) + res = super().serializable(prefix) + res['transcript'] = self.ipa + if self.accent != None: + res['accent'] = self.accent + res['sounds'] = snds + return res + + def __str__(self): + return json.dumps(self.serializable('')) + + def __eq__(self, other): + res = isinstance(other, self.__class__) and self.ipa == other.ipa and self.accent == other.accent and len(self.sounds)==len(other.sounds) + i = 0 + while res and i<len(self.sounds): + res = self.sounds[i] == other.sounds[i] + i += 1 + return res + +####### +# Metadata +## TODO: +# * POS : créer une classe POS avec les traits dépendants (ex: masc en fr) +####### + +####### +# Senses +# TODO: créer une classe Translations +####### + +class Definition(SubInfo): + prfx = "def" + key = "definition" + + def __init__(self, lang, text, prefix=None): + super().__init__(prefix) + if text != "": + self.lang = lang + self.text = text + else: + raise ValueError(f"Definition.__init__: “{text}†empty definition.") + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.lang == other.lang and self.text == other.text + + def serializable(self, prefix = None): + res = super().serializable(prefix) + res["lang"] = self.lang + res[self.__class__.key] = self.text + return res + +class Translation(Definition): + prfx = "trad" + key = "translation" + +class Example(SubInfo): + prfx = "ex" + + def __init__(self, transcript, source=None, url=None, prefix=None): + super().__init__(prefix) + if transcript != "": + self.text = transcript + self.source = source + self.url = url + else: + raise ValueError(f"Example.__init__: “{transcript}†empty example.") + + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.text==other.text and self.source==other.source and self.url==other.url + + def serializable(self, prefix = None): + res = super().serializable(prefix) + res["example"]=self.text + if self.source != None: + res["source"] = self.source + if self.url != None: + res["url"] = self.url + return res + +class Sense(SubInfo): + prfx = "" + + def __init__(self, lang=None, definition=None, wiki_lang=None, prefix=None): + self.lang = lang + self.label = None + self.set_id(prefix) + #On réinitialise les identifiants des sous-éléments + if not isinstance(self, SubSense): + Definition.reset() + Example.reset() + Translation.reset() + SubSense.reset() + + self.definitions = [] #liste des définitions (elles auront une langue et un texte) + self.subsenses = [] #liste des sous-définitions (récursif…) + self.examples = [] #liste des exemples (un texte obligatoire, source et url sont optionnels) + self.translations = [] #liste des traductions dans d'autres langues + self.domain = None #domaine d'usage du mot dans ce sens + if definition != None: + try: + self.add_def(wiki_lang, definition) + except ValueError as err: + raise ValueError(f"Sense.__init__() with empty definition\n{err}") + + def set_id(self, prefix=None): + if prefix != None and self.label == None: + self.label = f"{prefix}_{self.__class__.next_id}" #l'identifiant du sens + self.__class__.inc_n_id() + return self.label + + def get_id(self): + return f"{self.lang}.{self.label}" + + def set_domain(self, d): + self.domain = d + + def add_def(self, lang, definition): + theDef = Definition(lang, definition) + if theDef != None and theDef not in self.definitions: + theDef.set_id(self.set_id()) + self.definitions.append(theDef) + + def add_example(self, transcript, src=None, url=None, prefix=None): + try: + theEx = Example(transcript, src, url, prefix) + if theEx != None and theEx not in self.examples: + theEx.set_id(self.set_id()) + self.examples.append(theEx) + except ValueError as e: + print(f"Skipped empty example") + + def add_translation(self, lang=None, translation=None): + theTranslation = Translation(lang, translation) + if theTranslation != None and theTranslation not in self.translations: + theTranslation.set_id(self.set_id()) + self.translations.append(theTranslation) + + def add_subsense(self, subsense): + if self.label!=None: + subsense.set_id(self.set_id()) + if subsense not in self.subsenses: + self.subsenses.append(subsense) + + def __eq__(self, other): + res = isinstance(other, self.__class__) and self.label == other.label and len(self.definitions) == len(other.definitions) and len(self.examples) == len(other.examples) and len(self.translations) == len(other.translations) and self.domain == other.domain + i = 0 + while res and i < len(self.examples): + res = self.examples[i] in other.examples + i+=1 + i = 0 + while res and i < len(self.translations): + res = self.translations[i] in other.translations + i+=1 + i = 0 + while res and i < len(self.definitions): + res = self.definitions[i] in other.definitions + i+=1 + i = 0 + while res and i < len(self.subsenses): + res = self.subsenses[i] in other.subsenses + i+=1 + return res + + def serializable(self, prefix = None): + res = {} + if self.domain != None: + res["Domain"] = self.domain + if len(self.definitions) > 0: + res["Definitions"] = [] + for d in self.definitions: + res["Definitions"].append(d.serializable(prefix)) + if len(self.subsenses) > 0: + res["Subsenses"] = {} + for t in self.subsenses: + res["Subsenses"][t.set_id(self.label)]= t.serializable(prefix) + if len(self.examples) > 0 : + res["Examples"] = [] + for e in self.examples: + res["Examples"].append(e.serializable(prefix)) + if len(self.translations) > 0: + res["Translations"] = [] + for t in self.translations: + res["Translations"].append(t.serializable(prefix)) + return res + + def __str__(self): + return json.dumps(self.serializable()) + +class SubSense(Sense): + def set_id(self, prefix=None): + if prefix != None and self.label == None: + self.label = f"{prefix}.{self.__class__.next_id}" #l'identifiant du sens + self.__class__.inc_n_id() + return self.label + +class Entry: + #version_id : l'identifiant unique de la vesion de la page du wiktionnaire (pywikibot.Page.latest_revision_id) + def __init__(self, lemma, lang, wiki_lang, version_id, wkskt_version): + self.lemma = lemma + self.lang = lang + #Si un jour on mixe +ieurs données de plusieurs wiktionnaires, ce sera utile + self.sources = [] + self.sources.append({"wiktionary_language":wiki_lang,"permanentId":version_id,"wikstraktor_version":wkskt_version}) + self.current_source = 0 + self.pronunciations = [] + self.pos = None + self.senses = [] + #l'identifiant unique de la version de la page du wiktionnaire + Sense.reset() + + def set_pos(self, pos): + self.pos = pos + + def get_id(self, source_id=0): + #TODO: remplacer un jour le source id par la bonne source + if self.pos != None: + pos = self.pos + else: + pos = "" + return f"{self.lang}-{source_id}.{self.lemma}{pos}" + + def set_pronunciations(self, pron): + if isinstance(pron, Pronunciation): + self.add_pronunciation(pron) + elif type(pron) == list: + for p in pron: + if isinstance(p, Pronunciation): + self.add_pronunciation(p) + else: + raise ValueError(f"Entry.set_pronunciations: {p} is not a Pronunciation object ({p.__class__.__name__}).") + else: + raise ValueError(f"Entry.set_pronunciations: {pron} is not a Pronunciation object ({pron.__class__.__name__}).") + + def add_pronunciation(self, p): + if p not in self.pronunciations: + p.set_id(self.get_id()) + self.pronunciations.append(p) + + def set_senses(self, senses): + for s in senses: + if isinstance(s, Sense): + self.add_sense(s) + else: + raise ValueError(f"Entry.set_senses: {s} is not a Sense object ({p.__class__.__name__}).") + + def add_sense(self, s): + if s not in self.senses: + s.set_id(self.get_id()) + self.senses.append(s) + + def is_valid(self): + return self.lemma != None and len(self.pronunciations) > 0 and self.pos != None and len(self.senses) > 0 + + def __eq__(self, other): + res = isinstance(other, self.__class__) and self.lemma == other.lemma and self.lang == other.lang and self.pos ==other.pos and len(self.pronunciations) == len(other.pronunciations) and len(self.senses) == len(other.senses) + i = 0 + while res and i < len(self.senses): + res = self.senses[i] == other.senses[i] + i += 1 + i = 0 + while res and i < len(self.pronunciations): + res = self.pronunciations[i] == other.pronunciations[i] + i += 1 + return res + + def serializable(self, id=True): + res = {} + res['sources'] = self.sources + if id: + id = self.get_id() + res['id'] = id + else: + id == None + res[self.lemma] = {"pos":self.pos} + res[self.lemma]["pronunciations"] = [] + for p in self.pronunciations: + res[self.lemma]["pronunciations"].append(p.serializable(id)) + res[self.lemma]["senses"] = {} + for s in self.senses: + res[self.lemma]["senses"][s.get_id()]=s.serializable(id) + return res + + def __str__(self): + res = f"{self.lemma}_{self.lang} ({self.pos})\n" + for p in self.pronunciations: + res += f"{str(p)}\n" + for s in self.senses: + res += f"{str(s)}\n" + return res + +class ParserContext: + def __init__(self, entry, lang, wiki_lang, wversion_id, version_id): + self.lemma = entry + self.lang = lang + self.wiki_lang = wiki_lang + self.page_version_id = wversion_id + self.wikstraktor_version = version_id + self.context = [] + self.entries = [] + + def get_level(self): + if len(self.context) == 0: + res = -1 + else: + res = self.context[-1]["wiki"].level + return res + + def push(self, wiki_context): + self.context.append({"wiki":wiki_context}) + + def pop(self, testNewEntry = True): + if testNewEntry: + self.create_entries() + return self.context.pop() + + def flush(self): + while len(self.context) > 0: + self.pop(True) + + def set_top_wiki(self, wiki_context): + if len(self.context) == 0: + self.push(wiki_context) + else: + self.context[-1]['wiki'] = wiki_context + + def set_top_entry_info(self, key, entry_context, testNewEntry=True): + if len(self.context) == 0: + raise ValueError(f"Trying to set up entry info ({entry_context}), in an empty parserContext.") + else: + self.context[-1][key] = entry_context + if testNewEntry: + self.create_entries() + + def create_entries(self): + #In the key dict there are traits that describe every thing (ety, pro) and different entities (POS:senses) + tmp = {} + res = 0 + pro = None + for l in self.context: + for k,v in l.items(): + if k == "pro": + pro = v + elif k == "ety" or k == "wiki": + #wiki context is not necessary + pass #On ignore l'étymologie pour le moment + else: + tmp[k]=v + if(pro!=None and len(tmp)>0): + for pos,senses in tmp.items(): + e = Entry(self.lemma, self.lang, self.wiki_lang, self.page_version_id, self.wikstraktor_version) + e.set_pronunciations(pro) + e.set_pos(pos) + e.set_senses(senses) + #an improvement would be to remove that sense from context, but we test not to add doubles + if e.is_valid() and e not in self.entries: + res += 1 + self.entries.append(e) + return res + + def debug_top(self): + res = "Context: " + if len(self.context) == 0 : + res += "0" + else: + info = "" + for k,v in self.context[-1].items(): + if k != 'wiki': + if info != "": + info += "\n\t\t\t" + info += f"{k} → {str(v)}" + res += f"{len(self.context)*'='} {self.context[-1]['wiki'].level*'#'} {self.context[-1]['wiki'].title} / {info}" + return res + + def __str__(self): + res = "" + i=0 + for c in self.context: + res += f"====={i}======\n" + for k,v in c.items(): + if k!= "wiki": + res+=f" {k}→{v}\n" + else: + res+=f" {k}→{len(v)}\n" + i+=1 + return res+f"nb of entries: {len(self.entries)}" + + + +class Wikstraktor: + @classmethod + def get_instance(cls, wiki_language, entry_language): + try: + m_name = f"{wiki_language}_{entry_language}".capitalize() + instance = getattr(importlib.import_module(f"parsers.{m_name.lower()}"), f"{m_name}_straktor")() + instance.version = the_version + instance.log = Wikstraklog(the_version, entry_language, wiki_language) + except ModuleNotFoundError: + print(f"parsers.{m_name.lower()} module not found or {m_name}_straktor not found in module") + instance = None + return instance + + def __init__(self): + self.entries = [] + self.pwb = pywikibot + self.wtp = wikitextparser + self.parserContext = None + + def get_file_url(self, file_page_name): + res = None + try: + f = self.pwb.FilePage(self.site, file_page_name) + res = f.get_file_url() + except pywikibot.exceptions.NoPageError: + print(f"{file_page_name} does not exist in {self.site}.") + return res + + #retrieves the content of a page and processes it (adding the entries to the list of entries) + #returns the number of entries added + def fetch(self, graphy): + nb_entries_added = 0 + page = self.pwb.Page(self.site, graphy) + to_parse = [] + if page.text != "": + sections = self.wtp.parse(page.text).sections + found = False + i = 0 + ### find language + while i < len(sections) and not found: + found = sections[i].title != None and sections[i].title.capitalize() == self.constants[self.entry_language] + if not found: + i += 1 + if found: + nb_entries_added = self.parse(page.title(), page.latest_revision_id, sections[i].sections)#self.wtp.parse(s.contents).sections) + return nb_entries_added + + def parse(self, entry, v_id, sections): + self.parserContext = ParserContext(entry, self.entry_language, self.wiki_language, v_id, self.version) + self.log.set_context(entry, v_id) + for s in sections: + if s.title != None : + #handle wiki context + if self.parserContext.get_level() < s.level: + self.parserContext.push(s) + else: + while self.parserContext.get_level() > s.level: + self.parserContext.pop(True) + self.parserContext.set_top_wiki(s) + #get section title + stitle = self.wtp.parse(s.title).templates + if stitle == []: + stitle = s.title + else: + stitle = stitle[0].arguments[0].value + if self.isPro(stitle): + self.parserContext.set_top_entry_info('pro', self.process_pronunciation(self.wtp.parse(s.contents))) + elif self.isEty(stitle): + self.parserContext.set_top_entry_info('ety', self.process_etymology(self.wtp.parse(s.contents))) + else: + #Edit to process other types of sections + pos = self.process_POS(stitle) + if pos != None : + self.parserContext.set_top_entry_info(pos, self.process_senses(self.wtp.parse(s.contents))) + self.parserContext.flush() + res = len(self.parserContext.entries) + if res > 0: + for e in self.parserContext.entries: + self.entries.append(e) + return res + + def isPro(self, title): + if type(self.constants['pro']) == str: + res = title == self.constants['pro'] + else: + res = title in self.constants['pro'] + return res + + def isEty(self, title): + if type(self.constants['ety']) == str: + res = title == self.constants['ety'] + else: + res = title in self.constants['ety'] + return res + + #recognizes POS and returns None if it can't + def process_POS(self, parsedwikitext): + pass#in subclass + + def process_pronunciation(self, parsedwikitext): + pass#in subclass + + def process_etymology(self, parsedwikitext): + pass#in subclass + + #can be overloaded + def process_example(self, example_wiki_text): + k = 0 + isEx = 0 + res = None + #process templates + while k < len(self.wtp.parse(example_wiki_text).templates) and isEx == 0 : + if (self.wtp.parse(example_wiki_text).templates[k].normal_name() in self.constants['t_ex']): + res = self.wtp.parse(example_wiki_text).templates[0].arguments[-1].value + isEx = 1 + k += 1 + if isEx == 0: + res = self.wtp.parse(example_wiki_text).plain_text().strip() + return res + + #can be overloaded + def process_definition(self, definition, sub_items, def_level = True): + if def_level: + newSense = Sense(self.entry_language, self.wtp.parse(definition).plain_text().strip(), self.wiki_language) + pattern_ex = self.constants['sense_pattern'][0]["ex"] + pattern_subdef = self.constants['sense_pattern'][0]["add_subdef"] + self.constants['sense_pattern'][0]["def"] + else: + newSense = SubSense(self.entry_language, self.wtp.parse(definition).plain_text().strip(), self.wiki_language) + pattern_subdef = None + pattern_ex = self.constants['sense_pattern'][0]["add_subdef"] + self.constants['sense_pattern'][0]["ex"] + #Process examples + a = 0 + #print(newSense, sub_items)# DEBUG: + for item_list in sub_items: + if item_list.pattern == pattern_ex: + for item in item_list.items: + newSense.add_example(self.process_example(item)) + #Si on veut traiter les sous items (ex traductions), on peut utiliser + #item_list.sublists(a) + if def_level and item_list.pattern == pattern_subdef: + for item in item_list.items: + newSense.add_subsense(self.process_definition(item, item_list.sublists(a), False)) + a += 1 + return newSense + + def process_senses(self, sensesContent): + l = sensesContent.get_lists((self.constants['sense_pattern'][0]["def"])) + senses = [] + if len(l) > 1: + self.log.add_log("Wikstraktor.process_senses", f"===== WARNING ======\nmore than one sense list, make sure we don't forget anything\nignored lists : \n{l[1:]}\n===================") + l = l[0] #l now contains a list of list items + if l.pattern == self.constants['sense_pattern'][0]["def"]: + i = 0 + for item in l.items: + senses.append(self.process_definition(item, l.sublists(i))) + i += 1 + return senses + + def __str__(self): + return self.export() + + def export(self, id=True, ascii=False, compact=False): + res = [] + for e in self.entries: + res.append(e.serializable(id)) + if compact: + return json.dumps(res, ensure_ascii=ascii) + else: + return json.dumps(res, ensure_ascii=ascii, indent=4) + +if __name__ == "__main__": + import argparse + from argparse import RawTextHelpFormatter #pour le formattage de l'aide + parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description="""Interroger un wiktionnaire + \033[1m\033[32mex :\033[0m + ‣\033[0m\033[32m./wikstraktor.py -m blue\033[0m + ‣\033[0m\033[32m./wikstraktor.py -m blue -f blue.json -A -C\033[0m + ‣\033[0m\033[32m./wikstraktor.py -l en -w fr -m blue -f blue.json -n -A -C\033[0m""") + parser.add_argument("-l", "--language", help="la langue du mot", type=str, default = "en") + parser.add_argument("-w", "--wiki_language", help="la langue du wiki", type=str, default = "en") + parser.add_argument("-m", "--mot", help="le mot à chercher", type=str, default=None) + parser.add_argument("-f", "--destination_file", help="le fichier dans lequel stocker le résultat", type=str, default=None) + parser.add_argument("-A", "--force_ascii", help="json avec que des caractères ascii", action="store_true") + parser.add_argument("-C", "--compact", help="json sans indentation", action="store_true") + parser.add_argument("-n", "--no_id", help="json sans id", action="store_true") + args = parser.parse_args() + if args.mot != None: + w = Wikstraktor.get_instance(args.wiki_language, args.language) + resp = None + if w.fetch(args.mot) > 0: + resp = w.export(not args.no_id, args.force_ascii, args.compact) + if args.destination_file != None: + f = open(args.destination_file, "w") + f.write(resp) + f.close + else: + print(resp) + else: + raise NameError("Pas de mot demandé") diff --git a/templates/lexicon/show.html.twig b/templates/lexicon/show.html.twig index 8df5ddd900cb37d38dd221d6a74fe4dc53742e6b..f208143758d94ac1a3c50ba87e2a837e5a07fa83 100644 --- a/templates/lexicon/show.html.twig +++ b/templates/lexicon/show.html.twig @@ -35,7 +35,7 @@ <tr> <td>{{ entry }}</td> <td>{% for label in entry.headword.morphologicalLabels %}<span class="badge bg-primary">{{ label }}</span> {% endfor %}</td> - <td>{% for label in entry.headword.thematicLabels %}<span class="badge bg-secondary">{{ label }}</span> {% endfor %}</td> + <td>{% for label in entry.headword.generalLabels %}<span class="badge bg-secondary">{{ label }}</span> {% endfor %}</td> <td>{% for label in entry.headword.listLabels %}<span class="badge bg-info">{{ label }}</span> {% endfor %}</td> <td>{% for label in entry.headword.milestoneLabels %}<span class="badge bg-warning">{{ label }}</span> {% endfor %}</td> <td>