Add xml writer

pull/7890/head
Oliver Günther 5 years ago
parent 71eb1c2b41
commit 35d30e18af
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 3
      Gemfile
  2. 3
      Gemfile.lock
  3. 4
      modules/bcf/app/models/bcf/issue.rb
  4. 5
      modules/bcf/app/views/bcf/issues/_render_issues.html.erb
  5. 2
      modules/bcf/db/migrate/20191121140202_migrate_xml_viewpoint_to_json.rb
  6. 2
      modules/bcf/lib/open_project/bcf/bcf_json/viewpoint_reader.rb
  7. 67
      modules/bcf/lib/open_project/bcf/bcf_xml/base_writer.rb
  8. 2
      modules/bcf/lib/open_project/bcf/bcf_xml/exporter.rb
  9. 6
      modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
  10. 49
      modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb
  11. 182
      modules/bcf/lib/open_project/bcf/bcf_xml/viewpoint_writer.rb
  12. 6
      modules/bcf/spec/factories/bcf_viewpoint_factory.rb
  13. 18
      modules/bcf/spec/fixtures/viewpoints/full_viewpoint.bcfv.xml
  14. 6
      modules/bcf/spec/fixtures/viewpoints/minimal.bcfv.xml
  15. 2
      modules/bcf/spec/fixtures/viewpoints/neubau_sc_1.bcfv.xml
  16. 201
      modules/bcf/spec/fixtures/viewpoints/neubau_sc_1_fixed.bcfv.xml
  17. 0
      modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_reader_shared_examples.rb
  18. 4
      modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_reader_spec.rb
  19. 96
      modules/bcf/spec/lib/open_project/bcf/bcf_xml/viewpoint_writer_spec.rb
  20. 2
      modules/bcf/spec/requests/api/bcf/v2_1/viewpoints_api_spec.rb

@ -204,6 +204,9 @@ group :test do
gem 'retriable', '~> 3.1.1'
gem 'rspec-retry', '~> 0.6.1'
# XML comparison tests
gem 'compare-xml', '~> 0.66', require: false
gem 'rspec-example_disabler', git: 'https://github.com/finnlabs/rspec-example_disabler.git'
# brings back testing for 'assigns' and 'assert_template' extracted in rails 5

@ -397,6 +397,8 @@ GEM
colored2 (3.1.2)
commonmarker (0.20.1)
ruby-enum (~> 0.5)
compare-xml (0.66)
nokogiri (~> 1.8)
concurrent-ruby (1.1.5)
cork (0.3.0)
colored2 (~> 3.1)
@ -969,6 +971,7 @@ DEPENDENCIES
cells-erb (~> 0.1.0)
cells-rails (~> 0.0.9)
commonmarker (~> 0.20.1)
compare-xml (~> 0.66)
cucumber (~> 3.1.0)
cucumber-rails (~> 1.8.0)
daemons

@ -19,6 +19,10 @@ module Bcf
end
end
def imported_title
markup_doc.xpath('//Topic/Title').text
end
def markup_doc
@markup_doc ||= Nokogiri::XML markup, nil, 'UTF-8'
end

@ -6,11 +6,10 @@
<% highlighting_class << ' -failed' if issue.errors.present? %>
<div class="<%= highlighting_class %>">
<p>
<strong><%= issue.title %></strong>
<br/>
<% if issue.work_package %>
<%= link_to_work_package(issue.work_package) %>
<br/>
<% else %>
<%= issue.imported_title %>
<% end %>
</p>
<% if issue.errors.present? %>

@ -8,7 +8,7 @@ class MigrateXmlViewpointToJson < ActiveRecord::Migration[6.0]
# Convert viewpoints
::Bcf::Viewpoint.reset_column_information
::Bcf::Viewpoint.find_each do |resource|
mapper = ::OpenProject::Bcf::BcfJson::ViewpointMapper
mapper = ::OpenProject::Bcf::BcfJson::ViewpointReader
.new(resource.uuid, resource.viewpoint)
resource.update_column(:json_viewpoint, mapper.result)

@ -1,6 +1,6 @@
module OpenProject::Bcf
module BcfJson
class ViewpointMapper
class ViewpointReader
ROOT_NODE ||= 'VisualizationInfo'.freeze
attr_reader :uuid, :xml

@ -0,0 +1,67 @@
##
# Creates or updates a BCF issue and markup from a work package
module OpenProject::Bcf::BcfXml
class BaseWriter
attr_reader :markup_doc
def initialize
@markup_doc = build_markup_document
end
protected
def root_node
raise NotImplementedError
end
def root_node_attributes
{}
end
##
# Initial markup file as basic BCF compliant xml
def build_markup_document
Nokogiri::XML::Builder
.new(:encoding => 'UTF-8') do |xml|
xml.comment created_by_comment
xml.send(root_node,
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
**root_node_attributes)
end
.doc
end
def prepend_into_or_insert(parent_node, node)
if first_child = parent_node.children.select(&:element?)&.first
first_child.previous = node
else
node.parent = parent_node
end
end
def fetch(parent_node, name)
node = parent_node.at(name) || Nokogiri::XML::Node.new(name, markup_doc)
node.parent = parent_node unless node.parent.present?
node
end
##
#
def created_by_comment
" Created by #{Setting.app_title} #{OpenProject::VERSION} at #{Time.now} "
end
def to_bcf_datetime(date_time)
date_time.utc.iso8601
end
def to_bcf_date(date)
date.iso8601
end
def url_helpers
@url_helpers ||= OpenProject::StaticRouting::StaticUrlHelpers.new
end
end
end

@ -131,7 +131,7 @@ module OpenProject::Bcf::BcfXml
snapshot_file = File.join(issue_dir, vp.snapshot.filename)
# Copy the files
dump_file vp_file, vp.viewpoint
dump_file vp_file, ViewpointWriter.new(vp).to_xml
FileUtils.cp vp.snapshot.local_path, snapshot_file
files << vp_file << snapshot_file

@ -193,7 +193,7 @@ module OpenProject::Bcf::BcfXml
uuid: vp[:uuid],
# Save the viewpoint as json
viewpoint: viewpoint_as_json(vp[:uuid], vp[:viewpoint]),
json_viewpoint: viewpoint_as_json(vp[:uuid], read_entry(vp[:viewpoint])),
viewpoint_name: vp[:viewpoint],
# Save the snapshot as file attachment
@ -238,9 +238,9 @@ module OpenProject::Bcf::BcfXml
##
# Map the xml viewpoint as json
def viewpoint_as_json(uuid, xml)
::OpenProject::Bcf::BcfJson::ViewpointMapper
::OpenProject::Bcf::BcfJson::ViewpointReader
.new(uuid, xml)
.to_json
.result
end
def new_comment(comment_data)

@ -1,7 +1,7 @@
##
# Creates or updates a BCF issue and markup from a work package
module OpenProject::Bcf::BcfXml
class IssueWriter
class IssueWriter < BaseWriter
attr_reader :work_package, :issue, :markup_doc, :markup_node
TOPIC_SEQUENCE = [
@ -34,11 +34,11 @@ module OpenProject::Bcf::BcfXml
@work_package = work_package
@issue = find_or_initialize_issue
# Read the existing markup XML or build an empty one
@markup_doc = build_markup_document
# Create markup document
super()
# Remember root markup node for easier access
@markup_node = @markup_doc.at_xpath('/Markup')
@markup_node = markup_doc.at_xpath('/Markup')
end
def update
@ -58,7 +58,11 @@ module OpenProject::Bcf::BcfXml
issue.save!
end
private
protected
def root_node
:Markup
end
##
# Get the nokogiri document from the markup xml
@ -66,16 +70,7 @@ module OpenProject::Bcf::BcfXml
if issue.markup
Nokogiri::XML issue.markup, &:noblanks
else
build_initial_markup_xml.doc
end
end
##
# Initial markup file as basic BCF compliant xml
def build_initial_markup_xml
Nokogiri::XML::Builder.new do |xml|
xml.comment created_by_comment
xml.Markup "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema"
super
end
end
@ -124,12 +119,6 @@ module OpenProject::Bcf::BcfXml
end
end
def fetch(parent_node, name)
node = parent_node.at(name) || Nokogiri::XML::Node.new(name, markup_doc)
node.parent = parent_node unless node.parent.present?
node
end
def topic_attributes(topic_node)
topic_node['Guid'] = issue.uuid
topic_node['TopicType'] = work_package.type.name # TODO: Looks wrong to me. Probably better to use original TopicType?
@ -239,28 +228,10 @@ module OpenProject::Bcf::BcfXml
end
end
##
#
def created_by_comment
" Created by #{Setting.app_title} #{OpenProject::VERSION} at #{Time.now} "
end
##
# Find existing issue or create new
def find_or_initialize_issue
::Bcf::Issue.find_or_initialize_by(work_package: work_package)
end
def to_bcf_datetime(date_time)
date_time.utc.iso8601
end
def to_bcf_date(date)
date.iso8601
end
def url_helpers
@url_helpers ||= OpenProject::StaticRouting::StaticUrlHelpers.new
end
end
end

@ -0,0 +1,182 @@
##
# Creates or updates a BCF issue and markup from a work package
module OpenProject::Bcf::BcfXml
class ViewpointWriter < BaseWriter
attr_reader :viewpoint
def initialize(viewpoint)
@viewpoint = viewpoint
super()
end
def to_xml
doc.to_xml(indent: 2)
end
def doc
@doc ||= begin
viewpoint_node = fetch(markup_doc, root_node.to_s)
Nokogiri::XML::Builder.with(viewpoint_node) do |xml|
components xml
camera('orthogonal_camera', xml)
camera('perspective_camera', xml)
lines xml
clipping_planes xml
bitmaps xml
end
markup_doc
end
end
protected
def root_node
:VisualizationInfo
end
def root_node_attributes
{ Guid: viewpoint.uuid }
end
def dig_json(*args)
viewpoint.json_viewpoint.dig(*args)
end
def components(xml)
return unless dig_json('components')
xml.Components do
view_setup_hints xml
selected_components xml
visibility xml
coloring xml
end
end
def view_setup_hints(xml)
return unless (setup_hash = dig_json('components', 'visibility', 'view_setup_hints'))
xml.ViewSetupHints(camelized(setup_hash))
end
def selected_components(xml)
return unless (selected = dig_json('components', 'selection'))
xml.Selection do
selected.each do |comp_hash|
xml.Component camelized(comp_hash)
end
end
end
def visibility(xml)
return unless (visibility_hash = dig_json 'components', 'visibility')
xml.Visibility(DefaultVisibility: visibility_hash['default_visibility']) do
exceptions = visibility_hash['exceptions']
next unless exceptions
xml.Exceptions do
Array.wrap(exceptions).each do |comp_hash|
xml.Component camelized(comp_hash)
end
end
end
end
def coloring(xml)
return unless (colors = dig_json 'components', 'coloring')
xml.Coloring do
Array.wrap(colors).each do |color|
xml.Color Color: color['color'].delete_prefix('#') do
Array.wrap(color['components']).each do |comp_hash|
xml.Component camelized(comp_hash)
end
end
end
end
end
def camera(type, xml)
return unless (camera = dig_json(type))
xml.send(type.camelize) do
%w[CameraViewPoint CameraDirection CameraUpVector].each do |entry|
xml.send(entry) do
coords = camera[entry.underscore]
to_xml_coords(coords, xml)
end
end
xml.FieldOfView convert_float(camera['field_of_view'])
end
end
def lines(xml)
return unless (lines = dig_json 'lines')
xml.Lines do
Array.wrap(lines).each do |line|
xml.Line do
xml.StartPoint { to_xml_coords(line['start_point'], xml) }
xml.EndPoint { to_xml_coords(line['end_point'], xml) }
end
end
end
end
def clipping_planes(xml)
return unless (planes = dig_json 'clipping_planes')
xml.ClippingPlanes do
Array.wrap(planes).each do |plane|
xml.ClippingPlane do
xml.Location { to_xml_coords(plane['location'], xml) }
xml.Direction { to_xml_coords(plane['direction'], xml) }
end
end
end
end
def bitmaps(xml)
return unless (entries = dig_json 'bitmaps')
# Bitmaps are rendered flat, whyever that is
entries.each do |bitmap|
xml.Bitmaps do
xml.Bitmap bitmap['bitmap_type'].upcase
xml.Reference bitmap['bitmap_data']
xml.Location { to_xml_coords bitmap['location'], xml }
xml.Normal { to_xml_coords bitmap['normal'], xml }
xml.Up { to_xml_coords bitmap['up'], xml }
xml.Height convert_float(bitmap['height'])
end
end
end
##
# Helper to transform a hash into camelized keys
def camelized(hash)
hash.transform_keys(&:camelize)
end
##
# Convert a float to BCF format that strips
# insignificant zeros
def convert_float(val)
val.to_s.gsub(/(\.)0+$/, '')
end
##
# Helper to render X,Y,Z hash as set of nodes
def to_xml_coords(hash, xml)
hash.each do |key, val|
xml.send(key.to_s.upcase, convert_float(val))
end
end
end
end

@ -38,10 +38,10 @@ FactoryBot.define do
new_uuid = SecureRandom.uuid
uuid { new_uuid }
viewpoint_name { "full_viewpoint.bcfv" }
viewpoint do
file = OpenProject::Bcf::Engine.root.join("spec/fixtures/viewpoints/#{viewpoint_name}.xml")
json_viewpoint do
file = OpenProject::Bcf::Engine.root.join("spec/fixtures/viewpoints/#{viewpoint_name}.json")
if file.readable?
file.read
JSON.parse(file.read)
else
warn "Viewpoint name #{viewpoint_name} doesnt map to a viewpoint fixture"
end

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Created with the iabi.BCF library, V1.1.0 at 22.05.2017 09:51. Visit http://iabi.eu to find out more.-->
<VisualizationInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Guid="8dc86298-9737-40b4-a448-98a9e953293a">
<VisualizationInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Guid="{{UUID}}">
<Components>
<ViewSetupHints SpacesVisible="true" SpaceBoundariesVisible="true" OpeningsVisible="true" />
<Selection>
@ -120,13 +120,13 @@
</Location>
<Normal>
<X>-0.9999999999999999</X>
<Y>1.253656364893038E-16</Y>
<Z>0.0</Z>
<Y>1.253656364893038e-16</Y>
<Z>0</Z>
</Normal>
<Up>
<X>-5.43903050550883E-34</X>
<Y>-4.338533794284917E-18</Y>
<Z>1.0</Z>
<X>-5.43903050550883e-34</X>
<Y>-4.338533794284917e-18</Y>
<Z>1</Z>
</Up>
<Height>1666.1814563907683</Height>
</Bitmaps>
@ -140,13 +140,13 @@
</Location>
<Normal>
<X>-3</X>
<Y>1.253656364893038E-16</Y>
<Z>0.0</Z>
<Y>1.253656364893038e-16</Y>
<Z>0</Z>
</Normal>
<Up>
<X>30.1234</X>
<Y>-9000</Y>
<Z>1.0</Z>
<Z>1</Z>
</Up>
<Height>10</Height>
</Bitmaps>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<VisualizationInfo Guid="08a379aa-43d0-5160-3d92-1d16a24ed182">
<VisualizationInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Guid="{{UUID}}">
<OrthogonalCamera>
<CameraViewPoint>
<X>12.3456789</X>
@ -7,8 +7,8 @@
<Z>-1234.1234</Z>
</CameraViewPoint>
<CameraDirection>
<X>-1.0</X>
<Y>-2.0</Y>
<X>-1</X>
<Y>-2</Y>
<Z>-3</Z>
</CameraDirection>
<CameraUpVector>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<VisualizationInfo Guid="08a379aa-43d0-5160-3d92-1d16a24ed182">
<VisualizationInfo Guid="{{UUID}}">
<Components>
<Selection>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc$"/>

@ -0,0 +1,201 @@
<?xml version="1.0" encoding="UTF-8"?>
<VisualizationInfo Guid="{{UUID}}">
<Components>
<Selection>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc$"/>
<Component IfcGuid="2IzdhaXk1DxQeoZSeKbhcz"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyCk"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyCj"/>
<Component IfcGuid="2VwVShDiz0gAl$BsweUrbl"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDZ"/>
<Component IfcGuid="30Vi1xXgzEtA72FJfoLgwc"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyCe"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDR"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBr"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkb$"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDP"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBn"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDM"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkb_"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcN"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDK"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDJ"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcM"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcL"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcK"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkbZ"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkbY"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcF"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDB"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcD"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcC"/>
<Component IfcGuid="13osNZG6b1HvGnEwG5IBDr"/>
<Component IfcGuid="3W4DSy3pb33QWyGgzgo0pw"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc8"/>
<Component IfcGuid="0E1Yt2xX98svHrdrMFg9cE"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc7"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc6"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc5"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc4"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc2"/>
<Component IfcGuid="30Vi1xXgzEtA72FJfoLgxk"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc1"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkc0"/>
<Component IfcGuid="2PzCZPHLj8bQL_hp3fERU1"/>
<Component IfcGuid="1s$t1FRQf40wjxOyV1OVTV"/>
<Component IfcGuid="13osNZG6b1HvGnEwG5IBE9"/>
<Component IfcGuid="1pc0TFRcjAywrRV3HgpmMS"/>
<Component IfcGuid="0wh07UsgP8mf3ADo8LWq7U"/>
<Component IfcGuid="2VwVShDiz0gAl$BsweUrbX"/>
<Component IfcGuid="0wh07UsgP8mf3ADo8LWq6X"/>
<Component IfcGuid="3LnOhZYmz8UQwRTZK9roX$"/>
<Component IfcGuid="3W4DSy3pb33QWyGgzgo0mc"/>
<Component IfcGuid="2MXBDcWRP02P5tfnBdrHNU"/>
<Component IfcGuid="2MXBDcWRP02P5tfnBdrHNT"/>
<Component IfcGuid="3W4DSy3pb33QWyGgzgo0o9"/>
<Component IfcGuid="3W4DSy3pb33QWyGgzgo0mW"/>
<Component IfcGuid="2MXBDcWRP02P5tfnBdrHNJ"/>
<Component IfcGuid="3bII83hyD6Yu$Mpez2jNcv"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWOJ"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWOI"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWVj"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWVi"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWO6"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWO5"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWO4"/>
<Component IfcGuid="2UWgTxbmTEourPAlYFxD8x"/>
<Component IfcGuid="2UWgTxbmTEourPAlYFxD8v"/>
<Component IfcGuid="2UWgTxbmTEourPAlYFxD8u"/>
<Component IfcGuid="1mLIVAU39AxPo7CJmxmiBM"/>
<Component IfcGuid="1gOilIRmfEGwb7$SzUoanJ"/>
<Component IfcGuid="2OyfaLLJzADAvG7yl7qlqI"/>
<Component IfcGuid="2OyfaLLJzADAvG7yl7qlnw"/>
<Component IfcGuid="0e0$1G54f40AFFxJYqAOJk"/>
<Component IfcGuid="2OyfaLLJzADAvG7yl7qlnr"/>
<Component IfcGuid="2UWgTxbmTEourPAlYFxD8d"/>
<Component IfcGuid="0e0$1G54f40AFFxJYqAOJi"/>
<Component IfcGuid="2UWgTxbmTEourPAlYFxD8c"/>
<Component IfcGuid="2UWgTxbmTEourPAlYFxD8a"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWOz"/>
<Component IfcGuid="1nQPsl2Nv5Uvj6quEKe7LN"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWO_"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWQN"/>
<Component IfcGuid="1qQCUVUprErw_MLQ7DcWPL"/>
<Component IfcGuid="2OyfaLLJzADAvG7yl7qln3"/>
<Component IfcGuid="2OyfaLLJzADAvG7yl7qln0"/>
<Component IfcGuid="0mQ6Us0RbAEwcMtmD0TDm6"/>
<Component IfcGuid="2OyfaLLJzADAvG7yl7qlm1"/>
<Component IfcGuid="1pc0TFRcjAywrRV3HgppMl"/>
<Component IfcGuid="1pc0TFRcjAywrRV3HgppMh"/>
<Component IfcGuid="1pc0TFRcjAywrRV3HgppMf"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjh"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjg"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkiv"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjf"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkja"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkj7"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhV"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkj6"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhU"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhT"/>
<Component IfcGuid="0zsqKw7957Bh4NNjGz39d0"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhS"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkj3"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkj2"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhR"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhQ"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkj1"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkj0"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhP"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhO"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhM"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhL"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhK"/>
<Component IfcGuid="1gOilIRmfEGwb7$SzUohOS"/>
<Component IfcGuid="1azD0$4TTC1hgVDVWx5ITK"/>
<Component IfcGuid="1azD0$4TTC1hgVDVWx5ITI"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjX"/>
<Component IfcGuid="0LHXUGtOLEzvvIxaK5XMWV"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjW"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjT"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjS"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjR"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyAn"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyAl"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjN"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyAk"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjM"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjL"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyAi"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyD9"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjK"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyD8"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjJ"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkhi"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjI"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBV"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyAf"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjH"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkjG"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBR"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyAb"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyD2"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBQ"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBO"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBL"/>
<Component IfcGuid="2Cc$KVYf55mfjggfx$IIb9"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBK"/>
<Component IfcGuid="2Cc$KVYf55mfjggfx$IIb7"/>
<Component IfcGuid="2Cc$KVYf55mfjggfx$IIb6"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkbz"/>
<Component IfcGuid="0LHXUGtOLEzvvIxaK5XMW8"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBG"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkby"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBF"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkch"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBE"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBC"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcf"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkbu"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkbt"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyBA"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkca"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkbp"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkbo"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkbn"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkbm"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyB3"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkdD"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyB0"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcR"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcQ"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcP"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkh2"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkh1"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDt"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcv"/>
<Component IfcGuid="39_n9ZMhPAQBWcKbXoFkcu"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDq"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDk"/>
<Component IfcGuid="1Uhsb9oeb77OoM87lbwyDf"/>
</Selection>
</Components>
<PerspectiveCamera>
<CameraViewPoint>
<X>58.1038</X>
<Y>16.5493</Y>
<Z>24.7329</Z>
</CameraViewPoint>
<CameraDirection>
<X>1.28974</X>
<Y>1.2105</Y>
<Z>-0.569962</Z>
</CameraDirection>
<CameraUpVector>
<X>0.223629</X>
<Y>0.209889</Y>
<Z>0.951807</Z>
</CameraUpVector>
<FieldOfView>60</FieldOfView>
</PerspectiveCamera>
</VisualizationInfo>

@ -27,9 +27,9 @@
#++
require 'spec_helper'
require_relative './viewpoint_mapper_shared_examples.rb'
require_relative './viewpoint_reader_shared_examples.rb'
describe OpenProject::Bcf::BcfJson::ViewpointMapper do
describe OpenProject::Bcf::BcfJson::ViewpointReader do
let(:instance) { described_class.new xml_viewpoint.uuid, xml_viewpoint.viewpoint }
subject { instance.result }

@ -0,0 +1,96 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2019 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'compare-xml'
describe OpenProject::Bcf::BcfXml::ViewpointWriter do
let(:writer_instance) { described_class.new json_resource }
let(:reader_instance) { ::OpenProject::Bcf::BcfJson::ViewpointReader.new xml_resource.uuid, subject.to_xml }
let(:xml_comparison) { Nokogiri::XML(xml_resource.viewpoint) }
let(:json_comparison) { json_resource.raw_json_viewpoint }
subject { writer_instance.doc }
shared_examples 'converts back to xml' do
it 'the output of writer is XML-equal to the provided XML viewpoint' do
results = CompareXML.equivalent?(
subject,
xml_comparison,
collapse_whitespace: false,
verbose: true
)
if results.length > 0
puts subject.to_xml
raise "Expected documents to be equal. Found diffs:\n#{results.join("\n")}"
end
end
it 'contains the root node' do
expect(writer_instance.doc.at('VisualizationInfo')).to be_present
end
it 'goes full circle comparing back to JSON' do
expect(reader_instance.to_json).to be_json_eql json_comparison
end
end
describe 'with minimal example' do
let_it_be(:json_resource) do
FactoryBot.build_stubbed :bcf_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'minimal.bcfv'
end
let_it_be(:xml_resource) do
FactoryBot.build_stubbed :xml_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'minimal.bcfv'
end
it_behaves_like 'converts back to xml'
end
describe 'with full viewpoint' do
let_it_be(:json_resource) do
FactoryBot.build_stubbed :bcf_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'full_viewpoint.bcfv'
end
let_it_be(:xml_resource) do
FactoryBot.build_stubbed :xml_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'full_viewpoint.bcfv'
end
it_behaves_like 'converts back to xml'
end
describe 'with real-world neuhaus_sc_1 example' do
let_it_be(:json_resource) do
FactoryBot.build_stubbed :bcf_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'neubau_sc_1.bcfv'
end
let_it_be(:xml_resource) do
FactoryBot.build_stubbed :xml_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'neubau_sc_1_fixed.bcfv'
end
it_behaves_like 'converts back to xml'
end
end

@ -54,7 +54,7 @@ describe 'BCF 2.1 viewpoints resource', type: :request, content_type: :json, wit
shared_let(:bcf_issue) { FactoryBot.create(:bcf_issue_with_viewpoint, work_package: work_package) }
let(:viewpoint) { bcf_issue.viewpoints.first }
let(:viewpoint_json) { ::OpenProject::Bcf::BcfJson::ViewpointMapper.new(viewpoint).result }
let(:viewpoint_json) { ::OpenProject::Bcf::BcfJson::ViewpointReader.new(viewpoint).result }
subject(:response) { last_response }
describe 'GET /api/bcf/2.1/projects/:project_id/topics/:topic/viewpoints' do

Loading…
Cancel
Save